# Draw in Marble

Marble is a CAD editor for architectural drawings: plans, sections,
elevations and details. You draw and change a drawing with short
JavaScript scripts. Each call returns a checked report and a picture from the
same engine as the editor.

## Scripts

`marble_draw` takes a `script`. Every drawing function is in scope: no
imports, network, files or `save()`. Use plain loops and variables. Limits:
64 KiB of script, 2,000 objects, 16 MiB of memory and one second to run.

The first script draws the whole drawing. After that, pass the `revision`
and a script of only the calls to fix or add: a call with an object's name
replaces that object, and a call with a new name or no name adds one. Plain
numbers are in the drawing's unit. Do not send the whole script again. To
start over, draw a new drawing.

## Rules

- Points are `[x, y]`. +x is to the right and +y is up the picture: east
  and north in a plan, along the cut and height in a section or elevation.
- 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"`.

## Tasks

`marble_guide` returns this core and the part for one task. Read it again
with another task when the work changes.

<!-- task: sketch section -->
## From a drawing or sketch

- Before the script, look at each part of the source. Small marks are easy
  to miss in the whole sheet: if you can crop and enlarge the image, do it.
  List each part, and draw from the list.
- Written sizes and levels win over drawn lengths. A hand sketch is not to
  scale: use it for the arrangement. A drawing with a scale bar or a written
  scale is to scale: measure a size it does not write with that scale, and
  check it against the written sizes.
- Draw every part the source shows, at its size and position, with its line
  style: a dashed line stays dashed. Draw nothing it does not show: no
  rooms, furniture, fills or dimensions of your own.
- Draw a repeated part, such as a beam, column, mullion, tread or panel,
  with a loop: measure its spacing once.
- Give each kind of part its own layer. Draw what the cut passes through
  with heavier lines than what is seen beyond it.
<!-- /task -->

## You are done when

1. The first line of the report starts with ✓. ✓ means the drawing is
   sound, not that it is what the user asked for.
2. The drawing matches the request. In a plan, check each room and its size
   in the report, each door and the way it opens, and each window. From a
   drawing or sketch, check each item of your list, and compare the picture
   with the source part by part. A first draw that passes is a draft: fix
   what differs or is missing with a change script, and check again.

<!-- task: edit -->
## Edit a drawing

- `marble_open` returns the file: every object as a call with its name, in
  millimetres. Your change script's plain numbers are in the drawing's
  unit: the `display` on the file's first line, or millimetres if none.
- After the change, check the values in the file and the picture. Measure a
  clear gap between the facing edges, not the centres.
<!-- /task -->

<!-- task: section -->
## Sections and elevations

- Walls, rooms, doors and windows are plan objects. Draw a section with
  lines, polylines, rectangles, hatches and text.
- Put x = 0 at one end and y = 0 at a written datum, such as a floor level.
  Draw each written level at its height, with its level line and text.
- In your list, include each layer of a floor or roof build-up, each beam,
  column and footing, each frame and pane, and what is seen beyond the
  cut, such as a core, fittings or roof equipment.
- Draw the ground line as heavy as the cut parts.
<!-- /task -->

<!-- task: sketch request edit -->
## Plan rules

- 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.
- Use `wall: 'name'` on a door, window or opening to keep its host when walls
  overlap. Saved files include this reference.
- Draw the walls first. A door, window or opening goes on the wall under its
  centre point.
- Fill wall material with `hatch({ walls: [...] })`, not with boundary
  strips.
- A point hatch and a room's area take every line and shape around the point
  as an edge. Outlines that cross ("crosses … away from their ends"), or one
  inside another (a sofa on a rug), stop a point hatch; crossing outlines
  also leave a room's floor not closed. Do not trim, move, cut or leave out
  furniture for this: leave that hatch out, keep the room, and say so.
<!-- /task -->

<!-- task: sketch -->
## From a sketch

- In your list, include:
  - each dashed line. It is overhead, such as a roof edge or a beam, not a
    wall, and the floor under it stays open;
  - each room or space, with its written size;
  - which lines are walls. If the sketch draws walls thick or double, a
    thin single line is an edge: of a terrace, porch, deck or sit-out, a
    step or a counter. Draw an edge with `line` or `polyline`, not as a
    wall. If every line has the same weight, the lines that enclose rooms
    are walls;
  - each wall from end to end, and each break in it: a door (its leaf drawn
    open, as an arc or a slanted line; two leaves in one gap are a pair), a
    window (the wall drawn as thin lines or a thin box, with no leaf) or an
    opening (a plain gap);
  - each stair (a row of treads: evenly spaced lines across a narrow strip)
    and each part that projects past a wall line, such as a bay.
- Use the sketch's unit, such as `units('ft-in')`.
- Label each room with its name only, and give its written size as `size`;
  the drawn shape tells you which number runs east–west.
- A hand sketch is drawn freehand: its straight lines wobble, and parts
  that line up do not quite line up on paper. Draw what was meant: straight
  lines, square corners, and one x or y for parts that line up. Slant a
  wall only where the sketch clearly slants it.
- When the drawing is saved, Marble moves the walls so that each room gets
  its written size, and moves what is between them. So take from a hand
  sketch where each room is, which walls they share and which line up, and
  where the doors and windows are.
- If the report says a room is not its written size, the written sizes in
  that row of rooms do not add up. Keep the overall size, change the `size`
  of as few rooms as you can, and tell the user which.
- Draw an enclosed space with no name or door too, and the visible part of
  a room that the page edge cuts off. Mark a space with no name with
  `room('', point)`: the report measures it, and no label is drawn.
- Keep spaces open where the sketch leaves them open. Do not add a wall to
  separate two labels or to close a floor; the report then says which labels
  share one floor. Two labels on one floor measure between the same walls, so
  give a size to only one of them.
<!-- /task -->

<!-- task: request -->
## From a written request

- Before the script, plan in this order, and draw from the plan:
  1. List each room the user asks for, and each space the house needs to
     work, such as a passage or a utility. Give each a usual size from the
     Room sizes table. Add the areas and compare the total with
     the area you can build on. If it does not fit, make rooms smaller, but
     not narrower than the smallest width.
  2. Zones. The entrance opens into the living room or a foyer. Dining is
     next to the living room, and the kitchen next to the dining. Bedrooms
     are away from the entrance. Put the kitchen, utility and bathrooms
     side by side or back to back where you can, so they share pipes.
  3. Movement. Reach each room from the living, the dining or one short
     passage. Do not go through a bedroom to reach another room, except its
     own bathroom. A hall is a room you plan, not the space left over.
  4. Every bedroom, living room and kitchen has an outside wall with a
     window. A bathroom has an outside wall for its window.
- Do not cut the outline into equal strips of rooms. Give each room a
  rectangle with its long side at most 1.5 times its short side; a passage
  or a bathroom can be longer.
- A balcony, sit-out, porch or verandah is open on its outside sides. Draw
  those sides with `line`, not as walls.
- Put each door near a corner of its room, so the leaf opens against a
  wall. No leaf hits another leaf or a fixture.
<!-- /task -->

<!-- task: sketch request -->
## Drafting defaults

Use these unless the user asks for something else.

- Walls: 230 outside, 115 inside (9" and 4½").
- Hatches: none, unless the user asks for them or the sketch draws them.
- 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.
<!-- /task -->

<!-- task: request -->
## Room sizes

Use these for rooms the user does not size.

| Room | Smallest width | Usual size |
| --- | --- | --- |
| Living | 3000 | 3600 × 4200 to 4200 × 5400 |
| Dining | 2700 | 3000 × 3300 |
| Main bedroom | 3000 | 3600 × 3900 |
| Bedroom | 2700 | 3000 × 3300 |
| Kitchen | 1800 | 2400 × 3000 |
| Utility | 1200 | 1500 × 2100, beside the kitchen, with a door out |
| Bathroom | 1200 | 1500 × 2100 |
| Toilet only | 900 | 1000 × 1500 |
| Passage | 1000 | 1000 to 1200 wide |
| Balcony, sit-out | 1200 | 1500 deep |
<!-- /task -->

<!-- task: sketch request edit -->
## The report

```
✓ 5 walls, all joined · 2 doors · 2 windows · 1 opening · 2 blocks
Rooms:    "Living" ["living"] at (2000 mm, 1500 mm): 3828 mm (walls "outer 4", "hall") × 2770 mm (walls "outer 1", "outer 3"); 10.7 m²; walls "hall", "outer 1", "outer 3", "outer 4"; door "door 1"; window "window 1"
          "Kitchen" ["kitchen"] at (5500 mm, 1500 mm): 2828 mm (walls "hall", "outer 2") × 2770 mm (walls "outer 1", "outer 3"); 7.9 m²; walls "hall", "outer 1", "outer 2", "outer 3"; doors "door 1", "door 2"; window "window 2"; opening "opening 1"
```

- Each room has a label, an object ID in brackets, its label's point, its
  size, area and the names around its floor. Labels can repeat; use IDs to
  edit. Use the point and label to find the same room in the picture.
- The size is east–west × south–north through the label's point, between the
  faces of the nearest walls, which it names. Doors and windows count as their walls; furniture and lines do not stop it.
  In an open plan it runs to the walls beyond. Put the label where the sketch
  writes the size. "open to the north" means no wall stops it on that side.
- Area is the closed area around the label's point, as the Hatch tool finds
  it: walls, doors, lines and shapes close it; blocks do not. It is rounded
  and can include door thresholds. "one floor with" names the
  other labels on the same floor. "floor not closed" gives the Hatch tool's
  reason; close it only if the sketch closes it.
- Each problem names the objects and the fault, such as
  `wall "hall" ends 40 mm short of wall "outer 2"`. Fix those numbers with
  a change script. The report names a block that overlaps a wall; check other
  furniture in the picture.
- "reaches the outside" means the floor at that point is open. A door or
  opening closes a room.
- A hatch that cannot be drawn is a problem; the editor cannot draw it either.
  Leave it out and say so.
<!-- /task -->

## Functions

<!-- functions -->
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')`
- `remove(name)`: In a change: removes the object with that name. Example: `remove('sofa')`
- `rename(name, newName)`: In a change: gives the object a new name. Example: `rename('wall 3', 'kitchen east')`

### 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 | { walls: [name, ...] }, { pattern = 'Diagonal', scale = 1, angle = 0, name, layer, linetype, lineweight })`: A point fills a closed floor area; it does not cut out windows. In drawing scripts, { walls: ['wall name'] } fills the connected wall material, cuts out doors and windows, and follows their edits. A later wall fill replaces earlier fills on those connected walls. Patterns: Solid, Diagonal, Crosshatch, Brick, Concrete, Sand, Earth, Gravel, Steel, Insulation. Example: `hatch({ walls: ['outer 1'] }, { name: 'wall fill', 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 })`: Text centred on a point. Example: `text('North', [2000, 3500])`
- `room(label, centre, { size, name, layer })`: A room label at a point on clear floor. Labels may repeat. size is its written clear size, [east–west, south–north] between the wall faces through that point: Marble moves the walls to it. The report gives the area: the closed area around that point. Example: `room('Studio', [2000, 1500], { size: [3600, 2800] })`
<!-- /functions -->

A wrong argument stops the script with an error that says what to change.
`dimension('wall name')` measures a whole wall. A point dimension can keep
each end on a wall line: `dimension([100, 2000], [3900, 2000], { follows:
[['west', 'right'], ['east', 'left']] })`. Each entry is `[wall name,
'left' | 'right' | 'center']` or `null`; left and right follow the wall's
start-to-end direction.
