Deploy web Studio / deploy (push) Successful in 3m27s
House parts (stdlib isokit/house): house_wall drawn along a line, with ends that adapt at corners, T-junctions and straight runs so no two walls overlap; window and door that sit on a wall and cut its opening (door styles panel, glass, double, sliding, opening; swing and hinge by rotation); floor (boards, tiles, carpet, stone); roof (hip, gable, flat, optional solar); stairs. Furniture (stdlib isokit/furniture): chair, sofa / armchair, table with chairs, desk with monitor and chair, TV, bed, cupboard, bookshelf, kitchen run, bathtub, shower, toilet, washbasin, plant, rug, floor lamp. Cut-open levels: on gf / 1f the walls in front of a room are cut low (rooms are found by their floors), the roof becomes an outline on the floor, and storeys above are left out. A part's storey comes from z (storey_h, default 3 m). Scripting: objects_of, objects_at, objects_on, mount_of, mounted, frame, to_world, to_local, local_of. Prefab meta: shape (line / rect), mount, storey. Studio: drag to draw lines and rectangles (walls, roads, fences, hedges, floors, areas, buildings, roofs). The Room tool draws a floor plus walls, without doubling walls that already stand on an edge. Windows and doors snap onto walls with a hover preview, and they move, turn, copy and delete with their wall in one undo step. New parts land on the storey the view shows. Palette categories collapse, and the palette scrolls apart from the object list. The MCP instructions explain house building, and list_prefabs shows sits_on. examples/house is a furnished bungalow; the web Studio gets a House demo button. Tests: wall corners and junctions, zero depth-sort cycles in every view, cut levels, and the house demo in every view; check_studio covers the room / window / door / storey flow, check_web the House demo.
245 lines
13 KiB
Markdown
245 lines
13 KiB
Markdown
# isokit
|
||
|
||
Isometric scene engine for B4L projects: site plans, plants and buildings drawn
|
||
as SimCity-style views that you can pan, zoom, rotate, cut open by storey and
|
||
click for details. One Rust binary does everything:
|
||
|
||
- **Studio**: a GUI editor in the browser for people who don't code. You add
|
||
objects from a palette, drag them into place, and edit their properties in forms.
|
||
- **Web Studio**: the same Studio running entirely in the browser, with the
|
||
engine compiled to WebAssembly. Nothing to install: projects live in the
|
||
browser, or in a folder on disk (Chrome / Edge) that agents edit alongside.
|
||
- **Agent window**: chat with a language model right in the Studio (by default
|
||
B4L's inference server); it edits the scene with the MCP tools while you watch.
|
||
- **MCP server**: AI agents (Hermes and others) edit the same scene with the
|
||
same operations; their changes show up live in an open Studio.
|
||
- **Build**: a self-contained HTML page with every view prerendered, or
|
||
standalone SVGs for documents.
|
||
- **Build your own house**: walls, windows, doors, floors, roofs and stairs,
|
||
plus furniture (sofa, TV, bed, kitchen, table and chairs, desk, bath, …).
|
||
Draw rooms by dragging; windows and doors snap onto walls; the GF / 1F levels
|
||
cut the house open like a doll's house.
|
||
- **Engine and scripting**: everything you can place is a *prefab* written in
|
||
[Rhai](https://rhai.rs). Projects add their own prefabs, scripts and colours
|
||
without touching the engine.
|
||
|
||
The engine is a Rust port of the hand-written JS views in `cnx_vinyl`
|
||
(fleet calculator), `cnx_scuba` (centre view) and `mooban` (estate view),
|
||
and it keeps their look: same projection, depth sort, face shading, palette and
|
||
viewer behaviour.
|
||
|
||
## Quick start
|
||
|
||
```bash
|
||
cargo build --release # target/release/isokit.exe (one file, ~7 MB)
|
||
|
||
isokit new C:/dev/myproject/site-view # starter project
|
||
isokit studio C:/dev/myproject/site-view # opens the editor in the browser
|
||
isokit build C:/dev/myproject/site-view # dist/index.html, no server needed
|
||
```
|
||
|
||
Try the demos: `isokit studio examples/demo` (a works site) and
|
||
`isokit studio examples/house` (a furnished house built from parts).
|
||
|
||
### Studio (for everyone)
|
||
|
||
| Do | How |
|
||
|---|---|
|
||
| Add | pick an object on the left, click on the map (Shift-click places several) |
|
||
| Draw | walls, roads, fences, hedges: drag along the map; floors, areas, buildings, roofs, rugs: drag a rectangle |
|
||
| Select | click it on the map or in the object list |
|
||
| Move | drag it, or arrow keys by the snap step (0.1 / 0.5 / 1 / 5 m) |
|
||
| Turn | R / Shift+R or ⟲ ⟳ |
|
||
| Edit | the right panel: size, storeys, colours, solar, tooltip text, label, levels |
|
||
| Copy / delete | Ctrl+D / Delete; Ctrl+Z / Ctrl+Y undo and redo |
|
||
| Look | drag the ground to pan, Ctrl+wheel or pinch to zoom, ↺ ↻ rotate, Site / GF / 1F levels |
|
||
|
||
### Building a house
|
||
|
||
The **House** palette has the parts, **Furniture** the interior:
|
||
|
||
1. **Room**: drag a rectangle; you get a floor and a house wall on each edge
|
||
(walls already standing along an edge are not doubled). Or draw single
|
||
**House walls** by dragging along their line, and **Floors** as rectangles.
|
||
2. **Window** / **Door**: click on a wall. They snap onto its line, the wall
|
||
leaves the opening, and they move, turn, copy and delete with the wall. A
|
||
door opens to the side you clicked on; R turns it to the other hinge, then
|
||
to the other side. Styles: panel, glass, double, sliding, plain opening.
|
||
3. **Furniture**: chair, sofa / armchair, table with chairs, desk with monitor
|
||
and chair, TV, bed, cupboard, bookshelf, kitchen run (sink, hob, wall
|
||
cabinets, fridge), bathtub, shower, toilet, washbasin, plant, rug, floor
|
||
lamp. Fronts face local -y, so the back goes against a wall; R turns them.
|
||
4. **Roof** (hip, gable, flat, optional solar) as a rectangle over the walls,
|
||
and **Stairs**.
|
||
|
||
Level **GF** shows the house cut open: walls in front of a room are cut low
|
||
(walls tell inside from outside by the floors next to them), the roof becomes an
|
||
outline on the ground. For more storeys, view **1F** (2F, …): new walls, floors
|
||
and furniture go onto that storey (3 m apart, or the project parameter
|
||
`storey_h`), and the storeys below show closed.
|
||
|
||
Every change is saved to `scene.json` at once. When the file changes on disk
|
||
(an agent, git pull, another editor), the Studio reloads it, and that change can
|
||
be undone like any other.
|
||
|
||
### Web Studio (no install)
|
||
|
||
```bash
|
||
rustup target add wasm32-unknown-unknown
|
||
cargo install wasm-bindgen-cli --version 0.2.129 # must match Cargo.lock
|
||
python tools/build_web.py # -> dist/web/ (static files, ~2.8 MB, 0.75 MB gzipped)
|
||
```
|
||
|
||
`dist/web/` runs from any static web server (http or https, not `file://`).
|
||
A live build is at https://isokit.buildfor.life/: every push to `master` that
|
||
touches the engine or the front end rebuilds and deploys it
|
||
(`.gitea/workflows/deploy-web.yml`; server setup in `deploy/Caddyfile`).
|
||
The first visit opens a copy of the demo. Everything the desktop Studio does
|
||
works the same, because the page runs the same engine and answers the same
|
||
requests. In addition:
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Projects ▾** | list, open, delete; new project; demo copy; house demo; export as `.zip`; import a `.zip` or a project folder (drop it on the strip, or pick it) |
|
||
| **Open folder live** | Chrome / Edge: edits are written straight to the folder's files, and changes made there (an agent running `isokit mcp <folder>`, an editor, `git pull`) show up within 1.5 s and can be undone |
|
||
| **{ } Code** | edit `scene.rhai`, `lib/*.rhai` prefabs, `theme.toml`, `isokit.toml`; Ctrl+S applies, script errors show with file and line |
|
||
| **Build page** | downloads the self-contained HTML page (a folder project gets `dist/index.html` like `isokit build`) |
|
||
| **Preview** | the built page in a new tab |
|
||
|
||
Projects in the browser are stored in IndexedDB (per browser and site; export
|
||
them to keep a copy). Two tabs on the same project follow each other. The 15
|
||
MCP tools also run inside the page: `await isokit.mcp({jsonrpc: "2.0", id: 1,
|
||
method: "tools/list"})`, and browsers with WebMCP (`navigator.modelContext`)
|
||
get them as page tools.
|
||
|
||
### Agent window
|
||
|
||
**✦ Agent** in the Studio's top bar opens a chat with a language model that
|
||
edits the scene for you: "plant a row of trees along the road", "make the
|
||
workshop one storey taller", "what is on this site?". It works with the 15 MCP
|
||
tools below plus two page tools (point at an object, turn the view), streams
|
||
its answer and its reasoning, and shows each tool call (click one for the
|
||
arguments and the result, click an object id to select it). Every edit lands
|
||
on the map at once, and Undo takes it back. Stop ends a turn; the window moves
|
||
by its title bar and resizes at its top-left corner.
|
||
|
||
Any OpenAI-compatible endpoint with tool calling works (vLLM, llama.cpp,
|
||
Ollama, LM Studio, hosted APIs). The default is B4L's inference server,
|
||
`https://inference.p0.contact/v1`; ⚙ sets endpoint, key, model and whether
|
||
the model thinks first.
|
||
|
||
- `isokit studio` passes the calls through its own server (`/api/agent/*`), so
|
||
the key never reaches the page and the endpoint needs no CORS. Settings:
|
||
`ISOKIT_LLM_URL`, `ISOKIT_LLM_KEY`, `ISOKIT_LLM_MODEL`, or the window's ⚙,
|
||
which saves them in `%APPDATA%\isokit\agent.json` (`~/.config/isokit` elsewhere).
|
||
- The web Studio calls the endpoint from the page; the key stays in that
|
||
browser. The endpoint must allow the page (CORS, preflight included), or the
|
||
site relays it: https://isokit.buildfor.life/ is built with
|
||
`--agent-url /llm/v1`, a same-site path that Caddy forwards to B4L's
|
||
inference server (`deploy/Caddyfile`). The relay adds no key: each browser
|
||
enters its own once.
|
||
|
||
### Agents (MCP)
|
||
|
||
While the Studio runs, it also serves MCP over HTTP at `http://127.0.0.1:7878/mcp`.
|
||
Without the Studio, run `isokit mcp <project>` over stdio. In Hermes
|
||
(`config.yaml`):
|
||
|
||
```yaml
|
||
mcp_servers:
|
||
isokit:
|
||
command: C:\dev\isokit\target\release\isokit.exe
|
||
args: [mcp, C:\dev\myproject\site-view]
|
||
```
|
||
|
||
Tools: `project_info`, `list_prefabs`, `list_objects`, `get_object`,
|
||
`add_object`, `update_object`, `remove_object`, `duplicate_object`,
|
||
`apply_ops` (batch, one undo step), `set_params`, `render_view`,
|
||
`check_project`, `build_html`, `undo`, `redo`. Bad input comes back with a
|
||
message that says what is allowed, e.g. the list of types or a prop's range.
|
||
|
||
## A project
|
||
|
||
```text
|
||
isokit.toml title, levels, parameters, theme files, prefab libraries
|
||
scene.json the objects (edited by the Studio and agents)
|
||
scene.rhai optional script, drawn before the objects
|
||
lib/*.rhai optional project prefabs (listed under prelude)
|
||
theme.toml optional colours and materials
|
||
```
|
||
|
||
An object in `scene.json`:
|
||
|
||
```json
|
||
{ "id": "hall", "type": "building", "name": "Workshop", "x": -12, "y": 6, "rot": 90,
|
||
"props": { "w": 24, "d": 12, "floors": 2, "pv": true },
|
||
"tag": "Workshop", "info": "Two storeys, solar on the roof", "levels": [] }
|
||
```
|
||
|
||
Coordinates are metres: x points east, y north, z up. `x, y` is the centre of
|
||
the footprint, and `rot` turns the object in 90° steps. Levels: `site` (or any
|
||
other id) shows buildings closed, while `gf`, `1f`, `2f` … cut them open at that
|
||
storey.
|
||
|
||
## Commands
|
||
|
||
| Command | |
|
||
|---|---|
|
||
| `isokit new <dir>` | starter project |
|
||
| `isokit studio [dir] [--port N] [--no-open]` | GUI editor + `/mcp` + `/view` live preview + the agent window's link to the model |
|
||
| `isokit mcp [dir]` | MCP over stdio |
|
||
| `isokit build [dir] [-o file]` | self-contained HTML (all levels × 4 rotations) |
|
||
| `isokit render [dir] -o x.svg [--level gf] [--rot 2] [--light]` | one view as SVG |
|
||
| `isokit check [dir]` | lint: unknown types, props out of range, script warnings, NaN, node budget, unstyled classes |
|
||
| `isokit prefabs [dir]` | object types and their properties |
|
||
|
||
## Architecture
|
||
|
||
```text
|
||
crates/isokit-core projection (4 rotations), primitives, sprites, depth sort, SVG (no deps but serde)
|
||
crates/isokit-theme TOML palettes / materials / rules -> CSS; built-in default theme
|
||
crates/isokit-script Rhai runtime: drawing API, prefab library (stdlib/*.rhai), scene document driver
|
||
crates/isokit-project project folder, document ops, export, check, Workspace (undo, reload), MCP tools
|
||
crates/isokit-cli the `isokit` binary: Studio HTTP server, MCP stdio, build/render/check
|
||
crates/isokit-wasm the engine for the browser (wasm-bindgen): a project in memory behind the Studio API
|
||
viewer/ isokit-view.js/.css: pan, zoom, levels, rotation, tooltips (any page can embed it)
|
||
studio/ the Studio GUI (served by `isokit studio`, and by the web Studio); agent.js = agent window
|
||
web/ web Studio shell: WASM backend for studio.js, projects, folders, zip, code editor
|
||
examples/demo demo project (works site)
|
||
examples/house house built from parts (walls, windows, doors, floors, furniture, roof)
|
||
deploy/Caddyfile the web Studio's vhost on the buildfor.life web server (s1)
|
||
```
|
||
|
||
The layers are independent. A Rust program can draw with `isokit-core` alone.
|
||
`isokit-script` reads sources from memory, with no disk access, and a
|
||
`Workspace` reads and writes through a `Store` (a folder on disk, or memory),
|
||
so the same code runs natively and in the browser. The Studio, the web Studio
|
||
and MCP all apply the same `ops::Op` JSON operations through a `Workspace`, and
|
||
both Studio front ends speak the JSON API in `isokit_project::api`.
|
||
|
||
Writing prefabs and scripts: [docs/SCRIPTING.md](docs/SCRIPTING.md).
|
||
Plans: [docs/ROADMAP.md](docs/ROADMAP.md).
|
||
|
||
## Development
|
||
|
||
```bash
|
||
cargo test # engine, runtime, ops, demo, MCP
|
||
cargo clippy --all-targets -- -D warnings
|
||
ISOKIT_LLM_URL=http://127.0.0.1:7932/v1 ISOKIT_LLM_KEY=test-key ISOKIT_CONFIG_DIR=<scratch> \
|
||
isokit studio <copy of examples/demo> --port 7890 --no-open & # agent window on tools/mock_llm.py
|
||
uv run --with playwright python tools/check_studio.py <shot-dir> http://127.0.0.1:7890 # GUI + MCP + agent window end to end (Edge)
|
||
cargo clippy -p isokit-wasm --target wasm32-unknown-unknown -- -D warnings
|
||
python tools/build_web.py && uv run --with playwright python tools/check_web.py <shot-dir> # web Studio end to end
|
||
```
|
||
|
||
The agent window checks run on `tools/mock_llm.py`, a scripted stand-in for an
|
||
OpenAI-compatible server that the check scripts start on port 7932.
|
||
|
||
`python tools/build_web.py --skip-cargo` re-copies the front end (studio/,
|
||
viewer/, web/) without rebuilding the WASM. Code that runs in the browser must
|
||
not use `std::time` (it traps on wasm32-unknown-unknown): use `web_time`.
|
||
|
||
Set `ISOKIT_ASSETS=C:/dev/isokit` to serve `viewer/` and `studio/` from disk, so
|
||
you can edit the GUI without rebuilding. `tools/css2theme.py` converted the
|
||
original views' CSS into `crates/isokit-theme/themes/default.toml`.
|