Prompt-driven CAD: describe a part in words, an LLM writes it in a small modeling language, and it renders as an exact B-rep solid you can read, edit, and version.
Early and pre-release (v0.0.1). Expect rough edges and breaking changes.
The LLM doesn't emit a mesh or a script. It writes .ecky, which compiles through three layers:
- Surface — parenthesized Scheme:
(model (part ...)). - Core IR — the fixed set of operations the surface lowers to: primitives, booleans, selectors, placements, repeats. The kernel only sees this set.
- Backend — native OCCT by default (B-rep, selectable faces and edges). build123d and FreeCAD are follower backends for cross-check and import.
A parametric enclosure: named dimensions, a hollow body with a vent bored through it, a filleted top edge, and a verify clause that pins the lid clearance to a requirement.
(model
(params
(number body_w 80 :label "Body width" :min 40 :max 120 :step 1)
(number body_d 50 :label "Body depth" :min 30 :max 90 :step 1)
(number body_h 20 :label "Body height" :min 10 :max 40 :step 1)
(number wall 2 :label "Wall" :min 1 :max 5 :step 0.5)
(number vent_r 3 :label "Vent radius" :min 1 :max 6 :step 0.5))
; the lid must keep at least 0.3 mm clearance above the body
(verify
(tag lid_clearance body.lid_gap)
(metric gap (clearance min-distance body lid))
(expect gap (>= 0.3)))
(part body
(build
(shape hollow (shell wall :faces "top" (box body_w body_d body_h)))
(shape vent (translate 0 0 -0.5 (cylinder vent_r (+ body_h 1))))
(result
(fillet 1.5 :edges "top"
(difference hollow vent)))))
(part lid
(translate 0 0 (+ body_h 0.4)
(box (- body_w (* wall 2)) (- body_d (* wall 2)) 3))))params hoists dimensions to labelled sliders. build names each intermediate solid; difference bores the vent through the shelled body; fillet rounds the top edges. verify states an invariant — minimum lid clearance — that verify_generated_model checks red-to-green, independent of whatever geometry currently renders.
Smaller is fine too — (model (part p (sphere 10))) renders on its own. The Ecky IR Field Guide builds up from there, chapter by chapter.
- Version history, persisted in SQLite.
- Viewport screenshots fed back to the LLM between iterations.
- Fork a design into a new thread.
- Edit the generated IR by hand and commit it.
- LLMs: Gemini, OpenAI-compatible, Ollama.
- MCP server for external agents (inspect / validate / preview / commit).
- Node.js and Rust — for the Tauri/Svelte app.
- Python 3.10+ — used by the interop backends and runtime tooling.
- FreeCAD (optional) — only needed for the FreeCAD interop backend;
freecadcmdmust be on yourPATH.
The native OCCT kernel and build123d/speech runtimes are built locally by the prepare scripts below — you don't need a system FreeCAD for the default backend.
# 1. Clone, then install JS dependencies
npm install
# 2. Build the native CAD runtimes (OCCT kernel, build123d, speech)
npm run runtimes:prepare
# 3. Launch the desktop app in dev mode
npm run tauri devThen open the in-app settings (⚙️) to pick an LLM provider and add your API key. Start typing what you want to build.
Want just the web frontend without the desktop shell?
npm run devruns the Vite frontend and the Node server side by side.
API mode. Ecky calls an LLM provider directly with your key and generates .ecky from your prompt. Pick the provider and model in settings (⚙️). Gemini, any OpenAI-compatible endpoint, and local Ollama are supported.
Agent mode (MCP). Point an external coding agent at Ecky's built-in MCP server and it authors models with its own tools, going inspect → validate → preview → commit (workspace_overview to look around, macro_preview_render to preview an .ecky source, commit_preview_version to persist). Ecky can export a ready-made MCP skill bundle for the agent to load.
Smoke-test the MCP preview→commit path:
npm run mcp:smoke -- <thread-id> <path-to-model.ecky> [mcp-url]Full conventions are in AGENTS.md. The short version:
- Test-first (BDD dual-loop). A change starts from a failing integration test, driven inward through unit red-green-refactor. Run the relevant suite after each step.
- Conventional Commits.
type(scope): description; release-please reads them to compute versions and the changelog. - Tauri boundary. Frontend payloads are
camelCase, Rust structssnake_case; the contract layer translates. Regenerate the TS bindings withnpm run generate:contracts.
npm run test:unit # Svelte/TS unit tests
npm run test:e2e # Playwright end-to-end tests
npm run typecheck # svelte-check + tsc
cd src-tauri && cargo test # Rust backend tests