# Draw a floor plan in Marble

Marble is a browser CAD editor for architectural plans. You draw a plan by
running a short script. You do not need the editor or a browser.

## Quick start

In an empty folder, with Node 20+ or Bun:

```sh
curl -s -O https://app.marble.ad/agent/marble.mjs -O https://app.marble.ad/agent/marble.wasm
cat > plan.mjs <<'EOF'
import { walls, door, window, room, save } from './marble.mjs'
walls([[0, 0], [4000, 0], [4000, 3000], [0, 3000]], { closed: true })
door([1000, 0], 900, { opens: 'north' })
window([2800, 0], 1200)
room('Studio', [2000, 1500])
await save('plan')
EOF
node plan.mjs
```

Change `plan.mjs` and run `node plan.mjs` again until you are done.

### As an MCP server

For Claude Code, Codex or Claude Desktop, put `marble-mcp.mjs` beside
`marble.wasm`:

```sh
curl -s -O https://app.marble.ad/agent/marble-mcp.mjs -O https://app.marble.ad/agent/marble.wasm
```

- Claude Code: `claude mcp add marble -- node /full/path/marble-mcp.mjs`
- Codex: `codex mcp add marble -- node /full/path/marble-mcp.mjs`
- Claude Desktop: in `claude_desktop_config.json`, add
  `"marble": { "command": "node", "args": ["/full/path/marble-mcp.mjs", "/full/path/Marble"] }`.
  The second path is the folder for drawings.

The server saves drawings in the folder where the agent runs. The agent calls
`marble_guide`, then `marble_draw` for a new drawing, or `marble_open` and
`marble_change` to edit one that exists.

## You are done when

1. The first line of the report starts with ✓ and every room shows an area.
   ✓ means the drawing is sound, not that it is what the user asked for.
2. You have checked `<name>.png` against the request, one item at a time:
   every room and its size, every door and the way it opens, every window.
   From a sketch, end each room's label with its written size, such as
   `room('Bedroom 3000 x 3600', …)`: the report then names a room whose clear
   size differs. Fix what differs and run again.
3. Then give the user the `Open:` link, and say what you had to guess. If the
   report says the link is too long, give them `<name>.mbl`; they open it
   in Marble with Open.

Do not open a browser to check the result. The report and `<name>.png` come
from the same engine as the editor, and `save` reloads the file to prove it.

## Rules

- Points are `[x, y]`. +x is east, +y is north (the top of the picture).
- Plain numbers are millimetres. To work in another unit, call
  `units('ft-in')` (or `'m'`, `'cm'`, `'in'`) first: plain numbers are then in
  that unit (feet for `'ft-in'`), and dimensions and the report use it. A
  string with units works anywhere: `"12'7½\""`, `"3.2m"`, `"450mm"`, `"9in"`.
- A wall is its centre line; its thickness is split equally on both sides.
  Walls join where they share a point, or where one ends on another's centre
  line.
- Draw the walls first. A door, window or opening goes on the wall under its
  centre point.
- A room's area is the closed area around its point, as the Hatch tool finds
  it: walls, doors, lines and shapes close it; blocks do not.

## Functions

Grouped like the editor's dock.

The last object in each signature is optional. `name` is unique and the report uses it; `layer` is any layer name; `linetype` is one of 'continuous', 'dashed', 'dash_dot', 'center', 'hidden', 'phantom', 'dot', 'divide', 'border'; `lineweight` is in millimetres, one of 0, 0.05, 0.09, 0.13, 0.15, 0.18, 0.2, 0.25, 0.3, 0.35, 0.4, 0.5, 0.53, 0.6, 0.7, 0.8, 0.9, 1, 1.06, 1.2, 1.4, 1.58, 2, 2.11.

### Drawing
- `units(unit)`: The unit for plain numbers, and the drawing's display unit. Call it first. One of 'mm', 'cm', 'm', 'in', 'ft-in'; the default is 'mm'. A string with units, such as "12'7½\"" or "3.2m", works in any unit. Example: `units('ft-in')`

### Selection
- Select: Not needed to draw from scratch: every object has a name instead.
- Move, Rotate, Scale, Mirror: Not needed to draw from scratch: draw the object at its final position, size and direction.
- Array: Not needed to draw from scratch: repeat the call in a loop.

### Path
- `line(from, to, { name, layer, linetype, lineweight })`: A straight line. Example: `line([0, -1500], [4000, -1500], { linetype: 'dashed' })`
- `polyline(points, { closed, name, layer, linetype, lineweight })`: Straight lines joined end to end. Example: `polyline([[0, 0], [2000, 0], [2000, 1200]])`
- `arc(from, through, to, { name, layer, linetype, lineweight })`: A circular arc from one end, through a point on it, to the other end. Example: `arc([0, 0], [450, 450], [900, 0])`
- `curve(points, { closed, name, layer, linetype, lineweight })`: A smooth curve from the first point to the last; the points between guide it. Example: `curve([[0, 0], [1000, 800], [2000, 0]])`

### Shape
- `rectangle(corner, oppositeCorner, { name, layer, linetype, lineweight })`: A rectangle between two opposite corners. Example: `rectangle([500, 500], [1700, 1100])`
- `circle(centre, radius, { name, layer, linetype, lineweight })`: A circle. Example: `circle([1000, 1000], 400)`
- `polygon(centre, radius, sides, { name, layer, linetype, lineweight })`: A regular polygon with one corner due north of its centre. Example: `polygon([1000, 1000], 500, 6)`
- `ellipse(centre, width, height, { name, layer, linetype, lineweight })`: An ellipse; width runs east–west. Example: `ellipse([1000, 1000], 1200, 700)`

### Modify
- Offset, Extend, Trim, Join, Fillet, Chamfer: Not needed to draw from scratch: draw the finished shape directly.

### Architecture
- `wall(from, to, { thickness = 230, name, layer, linetype, lineweight })`: A straight wall along its centre line. Example: `wall([0, 0], [4000, 0], { thickness: 115 })`
- `walls(points, { closed, thickness = 230, name, layer, linetype, lineweight })`: One wall per pair of points, named "name 1", "name 2" and so on. Example: `walls([[0, 0], [4000, 0], [4000, 3000], [0, 3000]], { closed: true, name: 'outer' })`
- `stair(points, { width = 1000, going = 280, name, layer, linetype, lineweight })`: A stair walked up along its centre line: the foot, then the end of each flight. Flights run north, south, east or west, each a whole number of treads; a landing as deep as the stair is wide joins two flights. Example: `stair([[500, 500], [500, 3300], [2500, 3300]])`
- `door(centre, width = 900, { opens, hinge, style = 'single', name, layer, linetype, lineweight })`: A door in the wall under its centre. opens is the side the leaf swings to, across the wall ('north' or 'south' on an east–west wall). hinge is the jamb with the hinges, along the wall; the default is the west or south jamb. style is 'single', 'double' or 'sliding'; a sliding door needs no opens or hinge. Example: `door([1000, 0], 900, { opens: 'north' })`
- `window(centre, width = 1200, { type = 'sliding', name, layer, linetype, lineweight })`: A window in the wall under its centre. type is 'sliding', 'fixed' or 'casement'. Example: `window([2800, 0], 1200)`
- `opening(centre, width, { name, layer, linetype, lineweight })`: A gap in the wall under its centre, with no door. Example: `opening([0, 1500], 1000)`
- `block(block, centre, { back = 'north', name, layer, linetype, lineweight })`: A block from the library, with its back toward a compass direction. Blocks: Dining chair, Armchair, Sofa, 2 seats, Sofa, 3 seats, Corner sofa, Office chair, Dining table, 6, Round table, 4, Coffee table, Side table, Desk and chair, Single bed, Double bed, Queen bed, King bed, Nightstand, Wardrobe, Chest of drawers, Bookshelf, TV unit, Toilet, Basin, Bathtub, Shower tray, Sink and drainer, Hob, Fridge, North arrow. Example: `block('Double bed', [2000, 2000], { back: 'north' })`

### Annotation
- `dimension(from, to, { name, layer })`: A dimension, drawn on the outside of the plan. Example: `dimension([0, 0], [4000, 0])`
- `hatch(at, { pattern = 'Diagonal', scale = 1, angle = 0, name, layer, linetype, lineweight })`: Fills the closed area around a point, like the Hatch tool. Patterns: Solid, Diagonal, Crosshatch, Brick, Concrete, Sand, Earth, Gravel, Steel, Insulation. Example: `hatch([2000, 1500], { pattern: 'Brick' })`
- `guideline(from, through, { name, layer })`: An endless construction line through two points. Example: `guideline([0, 0], [1000, 1000])`
- `text(content, centre, { height = 250, name, layer, linetype, lineweight })`: Text centred on a point. Example: `text('North', [2000, 3500])`
- `room(label, centre, { name, layer })`: A room label at a point on clear floor. Labels may repeat. The report gives its area: the closed area around that point. A label that ends with a size, such as 'Bedroom 3000 x 3600', has the room's clear size checked against it. Example: `room('Studio', [2000, 1500])`

- `await save(name)`: writes `<name>.mbl` and `<name>.png`, prints the
  report, returns it (`ok`, `rooms`, `areaUnit`, `problems`, `files`, `link`,
  `text`), and starts a new drawing. `<name>.mbl` is gzip: read its text with
  `gunzip -c <name>.mbl`. Marble also opens a `.mbl` written as plain text.

A wrong argument stops the script with an error that says what to change.

A point dimension can keep each end on a wall line: `dimension([100, 2000],
[3900, 2000], { follows: [['west', 'right'], ['east', 'left']] })`. Each of the
two entries is `[wall name, 'left' | 'right' | 'center']` or `null`. Left and
right follow the wall's start-to-end direction. Wall edits update the points
in the same undo entry. Dragging an end detaches only that end. Deleting its wall
or making its line parallel to the measuring direction detaches the end and
keeps its last point.
The existing `dimension('wall name')` form still measures the whole wall.

## From a sketch

- A hand sketch is almost never to scale. Written sizes win over drawn
  lengths. Use the drawing only for the arrangement: where each room is,
  which walls they share, where the doors and windows are.
- Lay out the rooms from their written sizes, which are clear sizes between
  wall faces, plus the wall thicknesses between them. The drawn shape tells
  you which written number is the width.
- If the written sizes cannot all hold, keep the overall size, change as few
  rooms as you can, and tell the user which.
- Draw every enclosed space the sketch shows, even one with no name or door.
  Do not add doors or windows the sketch does not mark.

## Drafting defaults

Use these unless the user asks for something else.

- Walls: 230 outside, 115 inside (9" and 4½").
- Doors: entrance 1000, bedroom and kitchen 900, bathroom 750. A door opens
  into the room it serves.
- Windows: sliding. Living 1800, bedroom 1500, kitchen 1200, bathroom 600.
- Clear room sizes: living 3600 × 4200, bedroom 3000 × 3300, kitchen
  2400 × 3000, bathroom 1500 × 2100. Leave a 1000 passage where rooms open off
  a hall.

## The report

```
✓ 7 walls, all joined · 3 doors · 3 windows · 1 opening · 2 blocks
Rooms:    Living 12.3 m²: walls "hall", "outer 1", "outer 4"; door "door 1"; window "window 1"
          Kitchen 6.6 m²: walls "hall", "outer 1", "outer 2"; door "door 2"; window "window 2"
Files:    flat.mbl · flat.png
Open:     https://app.marble.ad/#plan=…
```

- Each problem names the objects and the fault, such as
  `wall "hall" ends 40 mm short of wall "outer 2"`. Change those numbers in
  the script and run it again. The report also names a room more than 50 mm
  off the size its label writes, a block in a door's swing, and a block that
  overlaps a wall or another block.
- A room with ⚠ has no area, and the report gives the Hatch tool's reason.
  "No closed area here" means the floor at that point reaches the outside:
  find the gap between walls. A door or opening closes a room.
- `<name>.png` has a grid of metres (feet in feet and inches) numbered on the
  edges. Hatches are grey.
