Files
isokit/README.md
T
grabowski a202283a60
Deploy web Studio / deploy (push) Successful in 3m46s
Web performance: light animation, gestures on a compositor layer, no forced layouts
Measured with the new tools/bench_web.py on the web Studio's house demo, CPU slowed 6x:

- Idle: running CSS animations repainted the whole drawing every frame (99% of the main
  thread in a window). Animations now step on about ten times a second by default
  ("light", 5% idle); the new toolbar button switches light / smooth / off and the browser
  remembers it; [build] animation sets the default for built pages; view.animation().
  Pan and zoom gestures pause them. Status LEDs pause off-screen and with the rest now.
- Pan 28 -> 10 ms, zoom 69 -> 11 ms per step: gestures move the drawing as one compositor
  layer (transform) and set the viewBox once they end; --iso-k is set on the tags group
  only and only when it changes; the stage box is read once per frame.
- Hover 22 -> 15 ms per move: the tooltip is placed once per frame on its own layer and
  measured only when its text changes; the Studio's x/y readout updates ten times a second
  in a contained box; hover work in the Studio runs once per frame.
- Long object lists skip layout of rows out of sight (content-visibility).
- WASM built with opt-level 3: engine render 11.7 -> 9.4 ms for 10% more download.

Checks: Studio 102/102 (animation modes, pan as a layer, contained readout), web 87/87.
2026-10-24 20:46:35 +07:00

14 KiB
Raw Blame History

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. 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

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, fences, hedges, roads: drag along the map, or click corner after corner for a run around a plot (double-click / Enter ends it, the first corner closes it); ends snap onto the walls and fences they meet. Floors, areas, buildings, roofs, rugs: drag a rectangle
Select click it on the map or in the object list; click a wall or fence again to select every piece joined to it
Several Ctrl+click (map or list) adds or takes out one, Shift+click in the list a range, Shift+drag on the map a box, Ctrl+A all; then drag one to move them all, arrows, R, Ctrl+D, Delete and the property form work on all of them
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.

Garden: the Pond (natural, round or square; stone or grass edge) has fish swimming round under lily pads, ripples and reeds; trees, hedges and rows of trees are in Nature.

Fences and walls around the plot: the Fence has six designs (mesh, wooden boards, steel bars, mesh on a stone base, cobble wall, brick wall with pillars). Fences and site walls whose ends meet join like house walls: corners close, a T stops at the other's face, a straight run shares one post, and a fence ending at a wall stops at its face.

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)

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):

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

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:

{ "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.

Animations (fans, smoke, fish, traffic) step on about ten times a second by default ("light"): running CSS animations repaint the whole drawing every frame, which keeps a slow laptop busy. The ▷ button in the view toolbar switches between light, smooth and off, and the browser remembers the choice. In isokit.toml, [build] animation = "smooth" sets the default for built pages.

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

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. Plans: docs/ROADMAP.md.

Development

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
uv run --with playwright python tools/bench_web.py --rate 6 --headed   # timings on a CPU slowed 6x (low-power stand-in)

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.