Examples
Every complete plan in the repository's examples/ directory, live and editable — edit the source on the left and the SVG on the right recompiles instantly (client-side, deterministic, no server). Hit Open in Playground to keep going with the full editor.
They are ordered as a tour rather than an inventory: a plan you can read in one screen first, then houses, then the large public buildings, then the drawings that exist to prove one language feature. Every figure quoted below comes from arch describe --json on the file itself, so the prose and the drawing cannot disagree.
Two things worth knowing before you start. A plan written as level blocks compiles to one drawing per storey, and a live widget shows page 1 — the lowest storey, which is what compile().svg returns; the whole set is in compile().pages, and arch describe --level 2 --json reads a specific floor. And the playground's theme selector restyles any plan — blueprint, dark, mono, presentation — without touching a line of its source, because a theme changes inks and hatches, never geometry.
The whole language on one sheet
Hillside Villa
The showpiece: a two-storey villa with an attached garage, drawn on one A2 sheet at 1:50. The widget below shows page 1 — the ground floor, 11 rooms, 196.54 m² (arch describe --json); the upper floor adds nine more rooms over 140.76 m² (arch describe --level 2 --json).
It is the plan to read when you want to see how the surfaces above fit together rather than in isolation: site { street north } names the facades a plot orientation lint rule can reason about; a room … polygon chamfers a reading nook off the study and an L-shaped master suite wraps its own ensuite; an arc wall edge bows a bay window off the living room; every door kind ships somewhere a builder would actually put it — hinged, sliding, pocket, bifold, barn; a stair carries the same id on both level blocks, so it is one shaft, not two symbols that happen to line up; a void opens a double-height gap over the living room; roof overhang draws the eaves on the upper storey; and a component authored once — ensuite() { … } — is placed twice with mirror x, so a pair of bathrooms share one definition and face opposite ways.
arch lint still raises three warnings on it, on purpose: a bathroom reachable only through a bedroom, and two bedrooms whose windows don't face the equator side. Every room is reachable and every doorway clears — the warnings are left in and named in the source as the honest cost of a real site, not something the plan was tuned to hide.
# The SHOWPIECE flagship — a two-storey hillside villa with an attached garage, the plan a
# first-time visitor to the language should see. Everything else in examples/ is one
# thing done well; this one is the whole language on one sheet. Surfaces it exercises:
# `site` (five derived compass names) · `paper`+`scale`+`axes`+`schedule`+`legend` (a real
# sheet with a title block) · `zone` (living/service/sleeping wings, rolled up in the
# schedule subtotals) · `room polygon` (a chamfered nook off the study, and the L-shaped
# master suite) · `arc` wall edge (a bowed bay off the living room) · every door kind —
# hinged/sliding/pocket/bifold/barn, each where a builder would actually put it · `window
# on <wall> at <pos>` plus a `for` loop (a street-facing ribbon of study windows) ·
# `level 1`/`level 2` · `stair` (same id both levels, one shaft) · `void` (a double-height
# gap over the living room) · `roof overhang` (the eaves, drawn on the upper storey) ·
# `component` + `place … mirror x` (a mirrored pair of ensuite bathrooms) · ~30 furniture
# families across kitchen/living/bedroom/study/garage/terrace · `dims auto all` plus one
# manual dim.
#
# The plot fronts the street on the NORTH, in the northern hemisphere — so `back` and
# `equator_side` are the SAME side (south), which is why the living room, the arc bay and
# every bedroom on the garden band take the south facade, and the garage/service band
# stacks along the street. `site` draws nothing; it only names directions.
plan "Hillside Villa" {
units mm
grid 100
paper A2 landscape
scale 1:50
north up
dims auto all
schedule rooms
legend
site { street north hemisphere north }
# A mirrored PAIR of ensuite bathrooms, authored once. `place ensuite() as <name> at
# (x,y) [mirror x]` instantiates it as an addressable box (its own wall + room + three
# wet fixtures); the parent cuts each suite's own door by a dotted reference into the
# instance's wall (`en2.wall`, `en3.wall`) — the same composition contract as
# `examples/museum-wings.arch`. No door is drawn INSIDE the component on purpose: which
# bedroom it opens into is the parent's decision, exactly like the wing's hall door.
component ensuite() {
wall id=wall partition thickness 100 { (0,0) (1800,0) (1800,2600) (0,2600) close }
room id=room at (0,0) size 1800x2600 label "Ensuite" uses bath
# `inset 150`, not `flush`, on the two NORTH fixtures: every placement backs that edge
# onto a THICKER host wall (`shell` 250 mm for en2/en3, `w_l2_band` 200 mm for en_m)
# running along the same line as this component's own 100 mm wall — `flush` reads the
# nearer face and collides with the far one. The shower goes top-RIGHT: every
# instance's bedroom door lands on the LEFT edge (`at 7500` below), and a shower there
# would leave it no swing room.
furniture shower in room anchor top-right inset 150 size 900x900
furniture basin in room anchor top-left inset 150 size 600x450
furniture wc in room anchor bottom-left flush size 400x700
}
# =====================================================================================
# LEVEL 1 — entry level: garage, study, living/dining/kitchen, terrace
# =====================================================================================
level 1 "Ground floor" {
# ---- shell ------------------------------------------------------------------------
# One closed ring for the main volume, with a bowed bay let into the south (garden)
# wall behind the living room — a curved arc EDGE, not a separate `room circle`,
# walked from (4400,10200) to (1600,10200) with `cw` so it bulges OUTWARD.
wall id=shell exterior thickness 250 {
(0,0) (13800,0)
(13800,10200)
(4400,10200)
arc (1600,10200) radius 2000 cw
(0,10200)
close
}
# The garage/utility wing: three sides only (its fourth side is the main shell's own
# west face, so nothing is drawn twice).
wall id=w_wing_n exterior thickness 250 { (-5500,0) (0,0) }
wall id=w_wing_w exterior thickness 250 { (-5500,0) (-5500,10200) }
wall id=w_wing_s exterior thickness 250 { (-5500,10200) (0,10200) }
wall id=w_util partition thickness 150 { (-5500,6200) (0,6200) }
# Front-band partitions (street side).
wall id=w_office_e partition thickness 100 { (4000,0) (4000,4200) }
wall id=w_powder_w partition thickness 100 { (8000,0) (8000,2200) }
wall id=w_powder_e partition thickness 100 { (10000,0) (10000,2200) }
wall id=w_frontsvc_s partition thickness 100 { (8000,2200) (13800,2200) }
wall id=w_entry_svc partition thickness 100 { (8000,2200) (8000,4200) }
# Front/back band spine, and the garden-band partitions.
wall id=w_l1_band_w partition thickness 200 { (0,4200) (8000,4200) }
wall id=w_l1_band_e partition thickness 200 { (8000,4200) (13800,4200) }
wall id=w_liv_din partition thickness 150 { (4800,4200) (4800,10200) }
wall id=w_din_kit partition thickness 100 { (8000,4200) (8000,8200) }
wall id=w_din_terr partition thickness 150 { (8000,8200) (8000,10200) }
wall id=w_kit_terr exterior thickness 200 { (8000,8200) (13800,8200) }
# ---- rooms, grouped into wings by `zone` -------------------------------------------
zone service "Service wing" {
room id=r_garage at (-5500,0) size 5500x6200 label "Garage" uses garage
room id=r_utility at (-5500,6200) size 5500x4000 label "Utility" uses utility
room id=r_powder at (8000,0) size 2000x2200 label "Powder" uses wc
room id=r_pantry at (10000,0) size 3800x2200 label "Pantry" uses storage
}
zone living "Living wing" {
# The study: a rectangle with its SE corner chamfered into a reading nook, an
# ANGLED room via `room polygon` — an exact shoelace area, label at the centroid.
room id=r_office polygon
(0,0) (4000,0) (4000,3400) (3200,4200) (0,4200)
label "Study" uses office
room id=r_entry at (4000,0) size 4000x4200 label "Entry" uses entry hall
room id=r_living at (0,4200) size 4800x6000 label "Living" uses living
room id=r_dining at (4800,4200) size 3200x6000 label "Dining" uses dining
room id=r_kitchen at (8000,4200) size 5800x4000 label "Kitchen" uses kitchen
room id=r_terrace at (8000,8200) size 5800x2000 label "Terrace"
# Behind Powder/Pantry, reached from Entry via `o_entry_svc` — gives their doors a
# real room on the other side (a door onto bare, room-less floor never registers in
# the access graph, even though it looks walkable on the sheet).
room id=r_service at (8000,2200) size 5800x2000 label "Service Hall" uses hall circulation
}
# ---- doors: one kind per situation -------------------------------------------------
door id=d_front at (6000,0) width 1200 wall shell hinge left swing into r_entry
door id=d_office at (4000,2000) width 900 wall w_office_e hinge left swing into r_office
door id=d_garage garage at (-2750,0) width 3000 wall w_wing_n
door id=d_garage_int at (0,2000) width 900 wall shell hinge right swing into r_office
door id=d_garage_util bifold on w_util at 2750 width 1500 swing in
door id=d_utility_ext at (-2750,10200) width 900 wall w_wing_s hinge right swing out
door id=d_powder pocket on w_frontsvc_s at 1000 width 800 slide right
door id=d_pantry barn on w_frontsvc_s at 3900 width 900 swing in
door id=d_kit_corr on w_l1_band_e at 2900 width 1200 hinge right swing into r_kitchen
door id=d_din_kit on w_din_kit at 2000 width 1000 swing into r_kitchen
door id=d_din_terrace at (8000,9200) width 900 wall w_din_terr hinge left swing into r_terrace
door id=d_kit_terrace sliding on w_kit_terr at 3000 width 2400 slide left
door id=d_living_garden at (800,10200) width 1000 wall shell hinge left swing out
opening id=o_liv_din on w_liv_din at 2000 width 3000
opening id=o_entry_svc on w_entry_svc at 1000 width 1800
opening id=o_entry_dining on w_l1_band_w at 5500 width 2400
# ---- windows: the ribbon of study windows is a `for` loop --------------------------
for i in 0..3 {
window on shell at (500 + i * 1100) width 700
}
window on shell at 11800 width 900 # pantry
window on shell at 19000 width 1200 # kitchen (east wall)
window on shell at 21000 width 1200 # kitchen (east wall)
window on shell at 23000 width 1400 # terrace (east wall)
window on shell at 17000 width 900 # corridor (east wall)
window at (6000,10200) width 1800 wall shell # dining, south wall
# ---- furniture ----------------------------------------------------------------------
furniture desk at (600,2700) size 1600x700 in r_office
furniture office_chair at (2300,2900) size 600x600 in r_office
furniture bookshelf at (200,3700) size 1600x300 in r_office
furniture rug at (600,5000) size 3000x2400 in r_living
furniture sofa_l at (700,5600) size 2400x1500 in r_living
furniture coffee_table at (1300,7200) size 1000x500 in r_living
furniture armchair at (3200,5750) size 900x850 rotate 270 in r_living
furniture plant at (300,4400) size 600x600 in r_living
furniture piano at (3200,4400) size 1300x1300 in r_living
furniture tv_unit against wall w_liv_din offset 3000 in r_living
furniture dining_table at (5600,6800) size 2000x1000 in r_dining
furniture counter against wall shell segment 1 offset 4900 size 1000x600 in r_kitchen
furniture kitchen_sink against wall shell segment 1 offset 5800 in r_kitchen
furniture stove against wall shell segment 1 offset 6500 in r_kitchen
furniture dishwasher against wall shell segment 1 offset 7100 in r_kitchen
furniture fridge against wall shell segment 1 offset 7700 in r_kitchen
furniture upper_cabinet against wall w_l1_band_e offset 4500 in r_kitchen
furniture island at (9200,5400) size 1800x900 in r_kitchen
furniture barstool at (8900,6500) size 400x400 in r_kitchen
furniture barstool at (9500,6500) size 400x400 in r_kitchen
furniture barstool at (10100,6500) size 400x400 in r_kitchen
furniture washer against wall w_wing_w offset 6800 in r_utility
furniture dryer against wall w_wing_w offset 7400 in r_utility
furniture wc in r_powder anchor bottom-left flush size 400x700
furniture basin in r_powder anchor top-right flush size 600x450
furniture car at (-4800,700) size 1800x4600 in r_garage
furniture car at (-2300,700) size 1800x4600 in r_garage
furniture sun_lounger at (9000,8700) size 1900x700 in r_terrace
furniture sun_lounger at (11200,8700) size 1900x700 in r_terrace
dim (8000,10500)->(13800,10500) offset 600 text "Terrace 5.8 x 2.0 m"
stair id=stair at (4900,2700) size 2200x1000 dir up
}
# =====================================================================================
# LEVEL 2 — sleeping level: two secondary suites, the master suite, and a gallery
# over the double-height living room
# =====================================================================================
level 2 "Upper floor" {
# ---- shell --------------------------------------------------------------------------
# No garage below this footprint and no bay above it: a plain closed rectangle, so its
# `roof overhang` below is a simple mitred offset with nothing curved to refuse.
wall id=shell exterior thickness 250 { (0,0) (13800,0) (13800,10200) (0,10200) close }
# ---- partitions -----------------------------------------------------------------------
wall id=w_bed2_s partition thickness 100 { (0,2600) (4000,2600) }
wall id=w_linen_s partition thickness 100 { (5800,2600) (8000,2600) }
wall id=w_bed3_s partition thickness 100 { (9800,2600) (13800,2600) }
wall id=w_l2_band partition thickness 200 { (0,4200) (13800,4200) }
wall id=w_gallery_e partition thickness 200 { (4800,4200) (4800,10200) }
zone sleeping "Sleeping wing" {
room id=r_bed2 at (0,0) size 4000x2600 label "Bedroom 2" uses bedroom
room id=r_linen at (5800,0) size 2200x2600 label "Linen" uses storage
room id=r_bed3 at (9800,0) size 4000x2600 label "Bedroom 3" uses bedroom
place ensuite() as en2 at (4000,0)
place ensuite() as en3 at (9800,0) mirror x
# The master suite: an L-SHAPED `room polygon` wrapping its own ensuite, which sits
# in the notch (the third, unmirrored placement of the same component) rather than
# being carved from a plain rectangle.
room id=r_master polygon
(4800,4200) (12000,4200) (12000,6800) (13800,6800) (13800,10200) (4800,10200)
label "Master Suite" at (7000,7500) uses bedroom
place ensuite() as en_m at (12000,4200)
}
room id=r_landing at (0,2600) size 13800x1600 label "Landing" uses hall circulation
# The gallery: it contains the void over the living room below, so it keeps its own
# full floor area even though a 3.0 x 4.2 m hole obstructs the middle of it.
room id=r_gallery at (0,4200) size 4800x6000 label "Gallery" uses hall circulation
# ---- doors ----------------------------------------------------------------------------
door id=d_bed2 on w_bed2_s at 2000 width 900 swing in
door id=d_bed3 on w_bed3_s at 2000 width 900 swing in
door id=d_linen on w_linen_s at 1100 width 700 swing out
# Pocket, not hinged: each ensuite is 1800 mm wide with a bed, wardrobe and this
# doorway all competing for the same wall — a swinging leaf has nowhere to clear.
door id=d_en2 pocket on en2.wall at 7500 width 700 slide right
door id=d_en3 pocket on en3.wall at 7500 width 700 slide right
door id=d_enm on en_m.wall at 7500 width 700 swing out
door id=d_master_corr on w_l2_band at 10500 width 1000 swing into r_master
opening id=o_gallery on w_l2_band at 1500 width 1800
# en2 and en3 also open directly onto the landing (their own SOUTH wall, local
# run-length 5300), so each reads as a shared/guest bath as well as an ensuite. `en_m`
# deliberately keeps its ONE door, into the master bedroom only: it sits in the
# building's far corner with no wall on circulation, and a master ensuite reached only
# through its own bedroom is the conventional case, not a defect — `arch lint` flags it
# (`W_BATH_VIA_BEDROOM`) and this is the one warning this plan leaves in, on purpose.
door id=d_en2_corr on en2.wall at 5300 width 700 swing out
door id=d_en3_corr on en3.wall at 5300 width 700 swing out
# ---- windows ----------------------------------------------------------------------------
window on shell at 1200 width 1400 # bedroom 2, north wall
window on shell at 11600 width 1400 # bedroom 3, north wall
window at (6800,10200) width 2400 wall shell # master suite, south wall
window at (0,7000) width 1400 wall shell # gallery, west wall
# ---- furniture --------------------------------------------------------------------------
furniture bed at (1250,150) size 1500x2000 in r_bed2
furniture nightstand at (700,150) size 450x400 in r_bed2
furniture wardrobe at (2900,300) size 550x1800 in r_bed2
furniture bed at (11050,150) size 1500x2000 in r_bed3
furniture nightstand at (12600,150) size 450x400 in r_bed3
furniture wardrobe at (13100,300) size 450x1800 in r_bed3
furniture rug at (6300,7400) size 3400x2400 in r_master
furniture double_bed at (7100,7600) size 1800x2000 in r_master
furniture nightstand at (6500,7600) size 450x400 in r_master
furniture nightstand at (9050,7600) size 450x400 in r_master
furniture wardrobe at (5000,4400) size 1800x600 in r_master
furniture armchair at (5100,9000) size 900x850 in r_master
# A reading nook in the otherwise-bare east corner of the L, south of the ensuite.
furniture desk at (10200,7100) size 1200x700 in r_master
furniture office_chair at (10500,7900) size 600x600 in r_master
# The gallery railing looks down into the double-height living room below — a `void`
# does not reduce this room's own area (`describe --json` reports it under `voids[]`).
void id=v_gallery at (900,5100) size 3000x4200
# The eaves: derived from THIS storey's own closed shell, mitred outward — it draws
# and nothing else, but it does grow the page.
roof overhang 700 wall shell
stair id=stair at (4900,2700) size 2200x1000 dir down
}
axes {
x at 0, 4000, 8000, 10000, 13800
y at 0, 2600, 4200, 10200
}
title {
project "Hillside Villa"
drawn_by "ArchLang"
date "2026-08-27"
}
}
Garden House
The other half of the language, and the one every plan above leaves out: everything outside the wall line. A two-storey family house on a 22 × 22 m suburban lot, A2 at 1:100 — 6 rooms and 136.5 m² of floor, beside 220.7 m² of ground (arch describe --json's totals.outdoor_area_m2) and a 484 m² lot (site.lot_area_m2). Those are three different numbers and the release exists to keep them apart: a terrace is not floor area, so a ground surface appears in no rooms[], no schedule rooms row and no access graph.
Twelve outdoor surfaces cover eight of the nine kinds — the driveway that runs to the garage door, paving to the front door, a patio behind the living room and a deck off the kitchen, a water pool, lawns front and back, gravel for the bin and shed yard, planting on three boundaries, and a balcony off the main bedroom whose railing is derived on the three edges with no wall behind them. Each draws a scale-aware material hatch, so the pattern is the same size on the sheet here as it would be at 1:50. Two fence runs post the lot: a panel enclosure round the pool — which is the drawing saying what the model does not, since ground obstructs nothing, water included — and a picket street frontage that stops either side of the path and the drive.
The garage is the other track meeting the first. room … uses garage classifies it and W_GARAGE_TOO_NARROW measures it (5.5 m across, against the 2.7 m one parked car needs), and its door is door garage — the sixth kind, which takes no clause at all and puts a dashed overhead projection inside the garage. Which side that is is derived rather than written: the resolver asks which face has floor, and the driveway on the other face is ground, not floor. Fifteen of the twenty-one outdoor fixture families furnish the garden, all at their catalogued footprints and none of them labelled.
arch lint raises two warnings, both left in and named in the source. One is an aspect heuristic (the one bedroom with no equator-facing window). The other is a finding: the bathroom opens straight off the landing, and describe --json's own doors[].between says so, but a balcony door counts as an exterior door, which grounds level 2 and stops the stair from being its arrival point — so the reachability walk starts inside a bedroom. The plan is right and the rule is not yet.
# The OUTDOOR flagship — a two-storey family house on a 22 x 22 m suburban lot, drawn as a
# SITE plan: the building is half of it, and the other half is ground.
#
# Everything else in examples/ draws what is inside the wall line. This one is the plan a
# house is actually issued as, which is the whole point of the v1.31 surfaces: `site …
# boundary` puts the lot line on the sheet, nine `outdoor` ground materials say what every
# square metre outside the walls IS, `fence` posts the pool enclosure and the street
# frontage, and 15 of the 21 outdoor fixture families furnish the garden the way the
# kitchen fixtures furnish a kitchen.
#
# READ THESE THREE FACTS BEFORE READING THE DRAWING. A ground surface is NOT a room: it is
# absent from `describe --json`'s `rooms[]`, from `totals.floor_area_m2` and from the drawn
# `schedule rooms` table, and its area lands in `totals.outdoor_area_m2` instead. It
# obstructs NOTHING — the pool included — so circulation walks straight across the water;
# that is a stated v1 simplification, and the `fence` round the pool is the drawing saying
# what the model does not. And it GROWS THE PAGE: the lot line is 22 m from the street, so
# without `paper` every line weight in the house would be scaled to the garden. Hence the
# sheet.
#
# THE GARAGE IS THE OTHER HALF OF THE RELEASE. `room … uses garage` is a use kind, so the
# schedule reads "Garage" and `W_GARAGE_TOO_NARROW` measures it: 5.5 m across, against the
# 2.7 m one parked `car` needs. Its door is `door garage`, the sixth kind — a sectional
# leaf that takes NO clause at all, because it parks OVERHEAD rather than swinging or
# sliding, and the dashed rectangle inside the garage is that overhead projection. Which
# side it projects to is DERIVED, not written: the resolver probes one wall thickness off
# each face and takes the one with floor. The driveway outside is `outdoor driveway`, and
# it is emphatically not floor, so the answer is the garage.
#
# ORIENTATION. The plot fronts the street on the NORTH, in the northern hemisphere, so
# `equator_side` is south — the garden. That is why the living room, the kitchen/dining
# and two of the three bedrooms take the south facade and the service band stacks along
# the street.
#
# ONE DELIBERATE WARNING, left in on purpose:
#
# W_ROOM_NOT_EQUATOR_FACING x1 — Bedroom 2 (level 2) is the one habitable room with no
# south window. On a two-band plan one bedroom has to take the street aspect, and the
# two rooms people sleep in longest get the garden. The rule is an advisory drafting
# heuristic, not a daylight measurement, and this is the case where the drafter
# overrules it. `arch validate` is clean; `arch validate --strict` reports this one.
plan "Garden House" {
units mm
grid 100
paper A2 landscape
scale 1:100
north up
dims auto overall
schedule rooms
legend
# The lot line. `street`/`hemisphere` draw nothing and only name directions; `boundary`
# is the one part of `site` that puts ink on the page (a dash-dot property line on
# C-PROP) and adds `site.lot_area_m2` — 484 m² here, by exact shoelace.
site {
street north
hemisphere north
boundary (0,0) (22000,0) (22000,22000) (0,22000)
}
# Shell centrelines. The building is an L: a 11.5 x 9 m house with a 5.5 x 6 m garage
# on its street-side flank.
let T = 300 # exterior thickness
level 1 "Ground floor" {
wall id=shell exterior thickness T {
(2500,4500) (19500,4500) (19500,10500) (14000,10500) (14000,13500) (2500,13500) close
}
wall id=w_h1 partition thickness 100 { (6800,4500) (6800,13500) }
wall id=w_h2 partition thickness 100 { (9200,4500) (9200,13500) }
wall id=w_w1 partition thickness 100 { (2500,9000) (6800,9000) }
wall id=w_e1 partition thickness 100 { (9200,8000) (14000,8000) }
wall id=w_gar partition thickness 200 { (14000,4500) (14000,10500) }
room id=r_hall at (6800,4500) size 2400x9000 label "Hall" uses hall entry circulation
room id=r_study at (2500,4500) size 4300x4500 label "Study" uses office
room id=r_living at (2500,9000) size 4300x4500 label "Living" uses living
room id=r_util at (9200,4500) size 4800x3500 label "Utility" uses utility wc
room id=r_kitchen at (9200,8000) size 4800x5500 label "Kitchen/Dining" uses kitchen dining
room id=r_garage at (14000,4500) size 5500x6000 label "Garage" uses garage
# The front door, on the street facade, into the spine.
door id=d_front on shell at 5500 width 1000 hinge left swing into r_hall
# THE GARAGE DOOR. No `hinge`, no `slide`, no `swing`, no `open` — every one of them is
# E_DOOR_KIND_CLAUSE, and each refusal is its own argument: the leaf travels UP, so
# there is no jamb to hinge on, no wall to slide along and no intermediate position to
# draw. The dashed rectangle it puts inside the garage is the panel parked overhead,
# and dashed is this language's one convention for anything above the cut plane.
door id=d_garage garage on shell at 14250 width 3000
door id=d_study on w_h1 at 2000 width 900 swing into r_study
door id=d_living on w_h1 at 6500 width 900 swing into r_living
# A pocket leaf: the utility takes no corridor, and the panel slides north into 1.3 m
# of blank spine wall (it needs 850 — its own 800 plus a jamb allowance).
door id=d_util pocket on w_h2 at 1800 width 800 slide left
# Kitchen to garage — the mud-room door every attached garage has.
door id=d_gar on w_gar at 1800 width 900 swing into r_util
# Living and kitchen are one space across the spine.
opening id=o_kitchen on w_h2 at 6800 width 1600
# The two garden doors, each the leaf a builder would put there. A `bifold` opens the
# living room onto the patio; a `sliding` pair opens the kitchen onto the deck. Neither
# sweeps anything — `doorSwing()` is null for every kind but `hinged` — so nothing on
# the terrace has to be kept clear of a leaf.
door id=d_patio bifold on shell at 41500 width 2000
door id=d_deck sliding on shell at 34200 width 1800 slide right
window id=win_study on shell at 49700 width 1600
window id=win_util on shell at 9100 width 900
window id=win_living on shell at 39400 width 1000
window id=win_kitchen on shell at 32400 width 1200
stair id=st at (6900,5200) size 900x3200 dir up
# ---- Inside ----
furniture desk in r_study anchor top-left flush size 1600x700
furniture bookshelf in r_study anchor bottom-left flush size 1800x350
furniture chair at (3300,6000) size 500x500 rotate 180 in r_study
furniture sofa against wall shell segment 5 offset 2100 size 2200x900 in r_living
furniture rug at (3200,10400) size 2200x1600 in r_living
furniture tv_unit at (5500,9200) size 1200x400 in r_living
furniture washer against wall shell segment 0 offset 7300 in r_util
furniture dryer against wall shell segment 0 offset 8100 in r_util
furniture basin against wall shell segment 0 offset 9900 in r_util
furniture wc against wall shell segment 0 offset 10900 in r_util
furniture kitchen_sink against wall w_e1 offset 600 in r_kitchen
furniture stove against wall w_e1 offset 1600 in r_kitchen
furniture fridge against wall w_e1 offset 2500 in r_kitchen
furniture dining_table at (10200,10800) size 1600x900 in r_kitchen
furniture chair at (10300,10100) size 500x500 rotate 180 in r_kitchen
furniture chair at (11100,10100) size 500x500 rotate 180 in r_kitchen
furniture chair at (10300,11800) size 500x500 in r_kitchen
furniture chair at (11100,11800) size 500x500 in r_kitchen
furniture car at (15400,5900) size 1900x4300 in r_garage
furniture bicycle at (18300,6200) size 600x1700 in r_garage
# ---- The ground ----
#
# Nine surfaces, six materials. None is a room, none obstructs anything, and each one
# draws a scale-aware hatch over a tint: the pattern is the same size ON THE SHEET at
# 1:100 as it would be at 1:50, because every dimension in it steps off the drawing's
# reference dimension rather than off millimetres of building.
outdoor id=g_drive driveway at (14500,0) size 5000x4500 label "Driveway"
outdoor id=g_path paving at (7300,0) size 1400x4500
outdoor id=g_frontl lawn at (600,600) size 6300x3400 label "Front lawn"
outdoor id=g_frontb planting at (8900,600) size 5400x3400
outdoor id=g_fronte planting at (19700,600) size 1700x3400
outdoor id=g_patio patio at (2600,13700) size 4300x3300 label "Patio"
outdoor id=g_link paving at (6900,13700) size 2300x3300
outdoor id=g_deck deck at (9200,13700) size 4800x2900 label "Deck"
outdoor id=g_pool water at (15200,13900) size 4000x3000 label "Pool"
outdoor id=g_lawn lawn at (5800,17400) size 13600x3600 label "Lawn"
outdoor id=g_yard gravel at (2600,17400) size 3000x3600
outdoor id=g_bed_s planting at (2600,21100) size 16800x600
outdoor id=g_bed_w planting at (600,13700) size 1700x8000
outdoor id=g_bed_e planting at (19800,13700) size 1900x8000
# The pool enclosure — a `panel` fence, which is what a pool barrier is. A fence is NOT
# a thin wall: no thickness, no poche, it hosts no opening and it joins no access
# graph, so `describe --json` reports it under `fences[]` and never under `walls[]`. A
# gate is deferred by name rather than faked by letting a door host onto it.
fence id=f_pool panel { (14800,13500) (19600,13500) (19600,17300) (14800,17300) close }
# The street frontage, in the other style. It stops either side of the path and the
# drive, which is how a picket fence meets an entrance.
fence id=f_street picket { (400,400) (7100,400) }
# ---- The garden ----
#
# Fifteen of the twenty-one outdoor families. Nothing here is `requiresWall`: outdoors
# the wall is the exception, and a fixture standing in the middle of a lawn is the
# normal arrangement rather than a floating one.
furniture outdoor_table at (2700,14400) size 1600x900
furniture outdoor_chair at (2900,13800) size 500x500 rotate 180
furniture outdoor_chair at (3700,13800) size 500x500 rotate 180
furniture outdoor_chair at (2900,15400) size 500x500
furniture outdoor_chair at (3700,15400) size 500x500
furniture umbrella at (4900,14400) size 900x900
furniture bbq at (6000,13900) size 700x600
furniture sun_lounger at (9600,14100) size 700x1900
furniture sun_lounger at (10600,14100) size 700x1900
furniture shed at (2900,19100) size 2400x1700
furniture bin at (2800,17600) size 600x600
furniture bin at (3600,17600) size 600x600
furniture clothesline at (2800,18400) size 2500x400
furniture ev_charger at (14000,2000) size 400x300
furniture mailbox at (6600,700) size 400x300
furniture trampoline at (9000,17800) size 3000x3000
furniture tree at (6100,18000) size 2400x2400
furniture tree at (17400,18100) size 2600x2600
furniture conifer at (20000,15000) size 1500x1500
furniture hedge at (2600,21200) size 16800x500
furniture shrub at (900,15000) size 900x900
furniture shrub at (900,17200) size 900x900
furniture shrub at (900,19400) size 900x900
furniture shrub at (20100,17600) size 900x900
furniture shrub at (20100,19600) size 900x900
furniture shrub at (20100,1300) size 900x900
furniture shrub at (9400,1300) size 900x900
furniture shrub at (12600,1300) size 900x900
}
level 2 "First floor" {
wall id=shell exterior thickness T {
(2500,4500) (14000,4500) (14000,13500) (2500,13500) close
}
wall id=w_h1 partition thickness 100 { (6800,4500) (6800,13500) }
wall id=w_h2 partition thickness 100 { (9200,4500) (9200,13500) }
wall id=w_w1 partition thickness 100 { (2500,9000) (6800,9000) }
wall id=w_e1 partition thickness 100 { (9200,8000) (14000,8000) }
# "Bedroom 1" IS the main bedroom — the one with the balcony. It is named for its
# number rather than its rank so the three read as a set, and so the label fits inside
# its own floor: the renderer has no font metrics, only the closed-form estimate in
# `text-metrics.ts`, and "Main bedroom" is wider than a 4.8 m room at 1:100.
room id=r_landing at (6800,4500) size 2400x9000 label "Landing" uses circulation hall
room id=r_bed2 at (2500,4500) size 4300x4500 label "Bedroom 2" uses bedroom
room id=r_bed3 at (2500,9000) size 4300x4500 label "Bedroom 3" uses bedroom
room id=r_bath at (9200,4500) size 4800x3500 label "Bathroom" uses bath
room id=r_bed1 at (9200,8000) size 4800x5500 label "Bedroom 1" uses bedroom
# No exterior door on this storey — see the header note. The stair below carries the
# same id, so the shaft is what joins this landing to the ground floor.
door id=d_bed2 on w_h1 at 2000 width 800 swing into r_bed2
door id=d_bed3 on w_h1 at 6500 width 800 swing into r_bed3
door id=d_bath on w_h2 at 1800 width 800 swing into r_bath
door id=d_bed1 on w_h2 at 6800 width 800 swing into r_bed1
# THE BALCONY, and the door that keeps it legal. An `outdoor balcony` rails every edge
# with no wall one thickness behind it — here the three free sides, derived at each
# edge's own midpoint — and `W_BALCONY_NO_DOOR` fires on a balcony with no opening
# within a wall thickness of it. This sliding leaf is that opening. It also sits over
# the ground floor's `deck`, which is what a balcony usually is.
door id=d_balcony sliding on shell at 23500 width 1800 slide right
window id=win_bed2 on shell at 1700 width 1600
window id=win_bath on shell at 8500 width 900
window id=win_bed3 on shell at 29900 width 1600
window id=win_bed1 on shell at 21500 width 1200
outdoor id=g_balcony balcony at (9300,13700) size 4200x1800 label "Balcony"
stair id=st at (6900,5200) size 900x3200 dir down
# Every bed goes `against wall`, not `anchor … flush`: `against` derives the quarter
# turn FROM the wall, so the headboard ends up at the wall. An anchored bed keeps
# rotation 0 whatever edge it is pushed to, which is `W_FIXTURE_BACK_TO_ROOM`.
furniture bed against wall shell segment 1 offset 6500 size 1600x2100 in r_bed1
furniture wardrobe in r_bed1 anchor top flush size 1600x600
furniture nightstand at (12600,9500) size 450x400 in r_bed1
furniture bed against wall shell segment 3 offset 6700 size 1400x2000 in r_bed2
furniture wardrobe in r_bed2 anchor top flush size 1400x600
furniture bed against wall shell segment 3 offset 2300 size 1400x2000 in r_bed3
furniture desk in r_bed3 anchor top flush size 1400x600
furniture bathtub in r_bath anchor top-right flush size 1700x700
furniture basin in r_bath anchor bottom-left flush size 600x450
furniture wc in r_bath anchor bottom-right flush size 400x700
# The eaves, on the top storey only — this level's `shell` is the plan's one closed
# exterior ring on THIS page, so no `wall` clause is needed. The garage is single
# storey and carries no eaves line of its own.
roof overhang 500
}
title {
project "Garden House"
drawn_by "ArchLang"
date "2026-08-28"
}
}
Start here
One room
The smallest complete ArchLang plan, and the one the guide opens with: a 200 mm shell, one room that says what it is for, a door and a window, and a single overall dimension chain. 1 room, 20 m², fourteen lines including its comment.
Neither opening carries a coordinate. door … on shell at 60% walks 60 % of the way around the shell wall's own run and puts the door there, so the door cannot be reported off-wall and cannot drift when the room resizes. Change 5000x4000 to something else and the wall, both openings and the dimension text all follow.
# The smallest complete ArchLang plan: one room, one door, one window, one dimension chain.
# Every other example is this file with more of it — a shell you can walk into, a room that
# knows what it is for, and openings pinned to the wall by position rather than coordinate.
plan "One Room" {
units mm
grid 50
north up
dims auto overall
wall id=shell exterior thickness 200 { (0,0) (5000,0) (5000,4000) (0,4000) close }
room id=r_main at (0,0) size 5000x4000 label "Room" uses living
door id=d_in on shell at 60% width 900 swing in # 60% of the loop's run: the south wall
window id=w_n on shell at 10% width 1500 # 10%: the north wall
title { project "One Room" drawn_by "ArchLang" date "2026-08-16" }
}
Laneway House
The signature example: a one-bedroom laneway cottage of 3 rooms and 48.75 m² (Living / Kitchen 27.3, Bedroom 13.2, Bath 8.25) with 4 doors and 4 windows, on a 7.5 × 6.5 m footprint. It is the plan the home page's hero types out.
Nothing in it is positioned by hand. Every opening is pinned to a run distance along a named wall (on w_lane at 2100), every fixture resolves against a room or the wall behind it (in r_live centered, against wall w_west offset 400), and the bath/bedroom pair is laid out by a strip that assigns each room its depth and derives the rest. site { street north } (site and orientation) names the two facades the plan turns on — the front door faces the lane, the glazing faces the garden — and it draws nothing at all; it only gives those directions names an intent or a lint rule can assert against. Two of the four doors are not hinged: a sliding garden door, and a pocket door to the bath whose slide right aims the panel down the solid length of the hall partition rather than at the 500 mm that would trip W_POCKET_RUN.
# A one-bedroom laneway cottage — 49 m² that reads as one program.
#
# Nothing here is positioned by hand. Every opening is pinned to a run distance along a
# named wall (`on <wall> at <pos>`), every fixture resolves against a room or a wall
# (`in <room> anchor …`, `against wall …`), and the bath/bedroom pair is laid out by
# `strip`. `site { street north }` names the two facades the plan turns on: the door
# faces the lane, the glass faces the garden.
plan "Laneway House" {
units mm
grid 50 # partitions are 100 thick, so a `flush` fixture lands on a …50
north up
dims auto all
site { street north }
wall id=w_lane exterior thickness 200 { (0,0) (7500,0) }
wall id=w_east exterior thickness 200 { (7500,0) (7500,6500) }
wall id=w_garden exterior thickness 200 { (0,6500) (7500,6500) }
wall id=w_west exterior thickness 200 { (0,0) (0,6500) }
wall id=w_hall partition thickness 100 { (4200,0) (4200,6500) }
wall id=w_bath partition thickness 100 { (4200,2500) (7500,2500) }
room id=r_live at (0,0) size 4200x6500 label "Living / Kitchen" uses living kitchen
strip down at (4200,0) gap 0 width 3300 {
room id=r_bath size 2500 label "Bath" uses bath
room id=r_bed size 4000 label "Bedroom" uses bedroom
}
door id=d_front on w_lane at 2100 width 900 hinge near start swing into r_live
door id=d_garden sliding on w_garden at 2700 width 1800 slide left
door id=d_bed on w_hall at 5600 width 800 hinge left swing into r_bed
# `w_hall` is walked lane→garden, so `slide right` sends the panel down the solid wall
# below the jamb; `slide left` would aim it at 500 mm and trip `W_POCKET_RUN`.
door id=d_bath pocket on w_hall at 900 width 800 slide right
window on w_west at 2400 width 1200
window on w_garden at 700 width 1000
window on w_garden at 5850 width 1600
window on w_east at 1500 width 600
furniture fridge against wall w_west offset 400 in r_live
furniture stove against wall w_west offset 1200 in r_live
furniture kitchen_sink against wall w_west offset 2000 in r_live
# The rug is an UNDERLAY, drawn before the pieces that stand on it so the sofa and
# table sit visibly on top rather than hiding it — anchored to the room, like
# everything else here, never a coordinate.
furniture rug in r_live anchor bottom-left inset 400 size 2800x3400
furniture table in r_live centered size 1300x900 label "Table"
furniture sofa in r_live anchor bottom-left flush inset 250 size 900x2000 label "Sofa"
furniture bed in r_bed anchor bottom-right flush inset 250 size 1500x2000 label "Bed"
furniture robe in r_bed anchor top-left flush inset 200 size 1800x600 label "Wardrobe"
furniture shower in r_bath anchor top-right flush size 900x900
furniture wc in r_bath anchor bottom-left flush size 400x700
furniture basin in r_bath anchor bottom-right flush size 600x450
title { project "Laneway House" drawn_by "ArchLang" date "2026-08-16" }
}
Garden Loft
3 rooms, 35 m², at scale 1:50 — deliberately the smallest plan still worth describing. It is the plan the home page reads its own room schedule from, which is the constraint that shaped it: three rooms, so the derived table fits in a band beside the drawing, but a real fit-out behind each one — a kitchen run with a wall behind it, a bath with three fixtures, a door into every room.
There is no paper block here, so scale 1:50 is annotation: the title block says 1:50 and the SVG scales to whatever box it is dropped into. That is what a web page wants; a building issued to a builder wants the sheet instead, which several examples below declare.
# A 35 m² garden loft — the plan the docs home page reads its own facts from.
#
# Deliberately the smallest plan worth DESCRIBING: three rooms, so `describe()`'s room
# table fits in a band beside the drawing, but a real one — a kitchen run with a wall
# behind it, a bath with three fixtures, a door into every room. `scale 1:50` is
# annotation here (there is no `paper` block), which is what a web page wants: the
# caption says 1:50 and the SVG scales to whatever box it is dropped into. Nothing but
# the walls carries a coordinate — openings are pinned to a run along a named wall and
# fixtures resolve against their room or the wall behind them.
plan "Garden Loft" {
units mm
grid 50
scale 1:50
north up
dims auto all
wall id=w_north exterior thickness 200 { (0,0) (7000,0) }
wall id=w_east exterior thickness 200 { (7000,0) (7000,5000) }
wall id=w_south exterior thickness 200 { (0,5000) (7000,5000) }
wall id=w_west exterior thickness 200 { (0,0) (0,5000) }
wall id=w_spine partition thickness 100 { (4200,0) (4200,5000) }
wall id=w_bath partition thickness 100 { (4200,3200) (7000,3200) }
room id=r_live at (0,0) size 4200x5000 label "Living / Kitchen" uses living kitchen
room id=r_bed at (4200,0) size 2800x3200 label "Bedroom" uses bedroom
room id=r_bath at (4200,3200) size 2800x1800 label "Bath" uses bath
door id=d_main on w_south at 3200 width 900 hinge near start swing into r_live
door id=d_bed on w_spine at 1600 width 800 hinge left swing into r_bed
door id=d_bath on w_spine at 4100 width 800 hinge right swing into r_bath
window on w_west at 3600 width 1400
window on w_north at 2400 width 1800
window on w_north at 5600 width 1200
window on w_south at 5000 width 600
furniture fridge against wall w_west offset 400 in r_live
furniture stove against wall w_west offset 1100 in r_live
furniture kitchen_sink against wall w_west offset 1900 in r_live
furniture table in r_live centered size 1300x900 label "Table"
furniture sofa in r_live anchor bottom-left flush inset 300 size 1900x850 label "Sofa"
furniture bed in r_bed anchor bottom-right flush inset 200 size 1500x2000 label "Bed"
furniture robe in r_bed anchor top-left flush inset 150 size 1200x600 label "Wardrobe"
furniture shower in r_bath anchor top-right flush size 900x900
furniture wc in r_bath anchor bottom flush size 400x700
furniture basin in r_bath anchor bottom-right flush size 600x450
title { project "Garden Loft" drawn_by "ArchLang" date "2026-08-16" }
}
Tiny House
A 7.2 × 3.0 m micro-home — 2 rooms, 19.6 m², 3 doors, 5 windows — and the example for the three language forms that are about fit rather than shape.
The wet room is 1.55 m wide, so a hinged leaf swinging in fouls the WC and swinging out sweeps the only walkway in the house; it gets a barn door instead, which sweeps nothing at all (doorSwing() returns null for every non-hinged kind, so W_SWING_OBSTRUCTED cannot even apply) and parks on the wall it came from. The wardrobe recess — 1.9 × 0.65 m, far under the 4 m² of floor lint expects of a room, so it is deliberately not declared as one — closes with a bifold: two leaves, each half of the 1600 opening, folding out of the way instead of swinging 800 mm into a 1.85 m galley. That 1.85 m is not typed anywhere: dim clear pulls each endpoint onto the inner face of the wall the measurement runs into, so the number on the drawing is the built clear width, derived. open 0.6 on the barn door is drawing-only — nothing measured reads it — and is written because a leaf drawn shut is indistinguishable from a hinged one on paper.
# A 7.2 x 3.0 m micro-home on one level — the DOOR-KINDS-AND-CLEARANCE example.
#
# A tiny house is the building where every millimetre is argued over, so it is the right
# plan to show the three language forms that are about *fit* rather than shape:
#
# door barn … open 0.6 a leaf that hangs on the FACE of the wall instead of in it
# door bifold … width 1600 two leaves that fold into their own opening
# dim clear (a)->(b) a dimension pulled in to the two INNER wall faces
#
# Why a barn door and not a hinged one? The wet room is 1.55 m wide. A hinged leaf
# swinging in fouls the WC; swinging out it sweeps the only walkway in the house. A barn
# leaf sweeps nothing at all — `doorSwing()` returns null for every non-hinged kind, so
# `W_SWING_OBSTRUCTED` cannot even apply to it — and parks on the wall it came from.
# `slide right` sends it toward the wall's far end (the same left/right convention
# `hinge` uses, read along the wall's own traversal direction), which here is the blank
# south-east corner of the living room rather than back over the loft ladder.
#
# `open 0.6` is DRAWING-ONLY. Nothing measured reads it: it changes how far along the
# wall the panel is drawn and nothing else. It is written here because a barn door drawn
# shut is indistinguishable from a hinged one on paper, and the point of the drawing is
# to show where the leaf goes when it is open.
#
# The wardrobe is a built-in recess, not a room — at 1.9 x 0.65 m it is far under the
# 4 m2 floor `lint` expects of a room, and calling it one would be a fiction. It is
# simply two partitions and a `bifold` door: two leaves, each half the 1600 opening,
# folding out of the way instead of swinging 800 mm into a galley that is 1.85 m wide.
#
# That 1.85 m is what `dim clear` reports. An ordinary `dim` measures between the two
# points you wrote; `clear` pulls each endpoint in onto the inner FACE of the wall the
# measurement runs into — here the galley face of the wardrobe front and the inner face
# of the north shell — so the number is the built clear width, derived, never typed.
# Take 600 mm of worktop out of it and 1250 mm of walkway is left, which is why the
# fridge door and the wardrobe leaves can both be open at once.
plan "Tiny House" {
units mm
# `grid 50`, not 100: every partition here is 100 thick, so a fixture placed `flush`
# against one lands on a half-thickness (...50) coordinate, and a 100 grid would snap
# it back INTO the wall.
grid 50
north up
# ---- shell + the two pieces of joinery that act as structure ---------------
# 7200 x 3000 over the outer faces; 6800 x 2600 inside. The shell is one closed
# outline so its corners mitre, and openings on it are written `at (x,y) wall shell`
# — an exact centreline point, which can no more be off-wall than `on … at <pos>` can.
wall id=shell exterior thickness 200 { (0,0) (7000,0) (7000,2800) (0,2800) close }
# The wet room's only partition. Named, because the barn door is hung on it by run
# distance from its first point, (5300,0).
wall id=w_wet partition thickness 100 { (5300,0) (5300,2800) }
# The wardrobe: a front and a return. Everything they enclose is still living-room
# floor as far as the plan is concerned — a recess, not a space.
wall id=w_ward partition thickness 100 { (0,2000) (2000,2000) }
wall id=w_ward_e partition thickness 100 { (2000,2000) (2000,2800) }
# ---- rooms ----------------------------------------------------------------
# One room does living, sleeping and cooking, which is what `uses living kitchen`
# says: it is a kitchen for the fixture rules and a living room for everything else.
room id=r_main at (0,0) size 5300x2800 label "Living / Sleep" uses living kitchen
room id=r_wet at (5300,0) size 1700x2800 label "Wet room" uses bath
# ---- doors ----------------------------------------------------------------
# The front door is the only hinged leaf in the house, and the only one with a swing
# arc to keep clear: 900 mm of quarter-disc between the wardrobe return and the bench.
door id=d_front at (2700,2800) width 900 wall shell hinge left swing in
# The wet room. `swing` on a barn door does not mean a sweep — there is none — it
# names the FACE the leaf hangs on, and `in` is the wall's +normal side: the living
# room, where there is 1600 mm of blank wall for the panel to park on.
# `open 0.6` draws the panel 60% of the way along that travel.
door id=d_wet barn on w_wet at 1500 width 800 swing in slide right open 0.6
# The wardrobe. A bifold's two leaves fold toward the `swing` face — out into the
# galley, because there is nothing behind them to fold into.
door id=d_ward bifold on w_ward at 1000 width 1600 swing out
# ---- windows --------------------------------------------------------------
window id=n_galley at (700,0) width 900 wall shell # over the sink
window id=n_bed at (3700,0) width 1200 wall shell # over the bed
window id=w_gable at (0,1400) width 900 wall shell
window id=s_bench at (4300,2800) width 1200 wall shell
window id=e_wet at (7000,1500) width 600 wall shell
# ---- galley: three catalogued fixtures, no coordinates --------------------
# `offset` is the distance of the piece's CENTRE along shell segment 0 — the north
# run, walked from (0,0) — and the footprints come from the catalogue, so no width,
# depth or wall face is written down twice.
furniture kitchen_sink against wall shell segment 0 offset 500 in r_main
furniture stove against wall shell segment 0 offset 1200 in r_main
furniture fridge against wall shell segment 0 offset 1800 in r_main
# ---- wet room -------------------------------------------------------------
# Anchored to the room and `flush`, so each piece measures from the backing wall's
# inner face rather than from a half-thickness someone worked out by hand.
furniture shower in r_wet anchor top-left flush size 900x900
furniture wc in r_wet anchor bottom-left flush size 400x700
# The basin is the one fixture placed by coordinate, and therefore the one that has to
# state its own quarter-turn. `rotate 0` means "back to the north", so `rotate 90` puts
# this basin's back on its EAST edge — against the shell, in the south-east corner the
# shower does not take. Note the declared `size` is always the plan-axis footprint:
# the symbol turns inside it, the rectangle does not.
furniture basin at (6450,2100) size 450x600 rotate 90 in r_wet
# ---- living / sleeping ----------------------------------------------------
furniture bed at (2600,100) size 2000x1400 label "Bed" in r_main
furniture bench at (3400,2100) size 1500x600 label "Bench" in r_main
furniture wardrobe at (100,2350) size 1800x350 label "Wardrobe" rotate 180 in r_main
# The loft ladder's hatch, flush to the wet-room partition. 600 wide because a label
# is drawn INSIDE its own footprint: "Ladder" does not fit a 350 mm box, and the
# renderer has no metrics to shrink it with — it would simply run out over the wall.
furniture ladder at (4650,100) size 600x700 label "Ladder" in r_main
# ---- dimensions -----------------------------------------------------------
# Hand chains rather than `dims auto`: on a plan this small the automatic GB/T set
# would out-draw the building. Endpoint ORDER picks the side — the line is offset
# along the LEFT normal of from->to — so each of these is written in the direction
# that throws it out into the margin. The clear dim carries no `text`: the whole point
# is that 1850 is measured off the two wall faces, not typed in by the author.
dim clear (200,0)->(200,2000) offset 600
dim faces (0,2800)->(7000,2800) offset 500
dim faces (7000,2800)->(7000,0) offset 500
# ---- the eaves --------------------------------------------------------------
# `shell` is the plan's one closed exterior ring, so no `wall` clause is needed to
# disambiguate it. A micro-home wears its roof close: 400 mm, not the 600 a suburban
# bungalow shades its glazing with.
roof overhang 400
title {
project "Tiny House"
drawn_by "ArchLang"
date "2026-08-16"
}
}
Studio (1BR)
The long-standing flagship: 4 rooms, 42 m², 3 doors, 3 windows, scale 1:50. A tour of rooms tagged with uses, a cased opening linking the living space to the hall, real fixture symbols (sink, counter, stove, fridge, shower, basin, WC), and doors whose swings stay clear of the furniture. It is lint-clean under the default profile and import-free — a single file with no import and no World seam — which is why the test suite uses it as the plan that must always compile from nothing. Run arch describe / arch lint on it to see the analysis layer in action.
# A compact studio apartment — the canonical ArchLang example.
#
# Architecturally sound (passes `arch lint`): every room opens off a central hall,
# so the bath is never reached through the bedroom; the bath is fully enclosed and
# fitted with real fixtures; and no door leaf sweeps onto furniture. Self-contained
# (no imports) so it compiles from a single file.
plan "Studio 1BR" {
units mm
grid 50
scale 1:50
north up
# Exterior shell + partitions. The x=4000 divider runs the FULL height so the bath
# is walled off from the living space; the right column splits into bedroom / hall / bath.
wall exterior thickness 200 { (0,0) (7000,0) (7000,6000) (0,6000) close }
wall partition thickness 100 { (4000,0) (4000,6000) }
wall partition thickness 100 { (4000,3000) (7000,3000) }
wall partition thickness 100 { (4000,4400) (7000,4400) }
room id=r_living at (0,0) size 4000x6000 label "Living / Kitchen" uses living kitchen
room id=r_bed at (4000,0) size 3000x3000 label "Bedroom" uses bedroom
room id=r_hall at (4000,3000) size 3000x1400 label "Hall" uses hall
room id=r_bath at (4000,4400) size 3000x1600 label "Bath" uses bath
# Entrance into the living space; the hall links it to the bedroom and the bath.
# Living ↔ hall is a cased opening (circulation needs no door leaf); the bedroom
# and bath each get a real door off the hall.
door id=d_main at (3000,6000) width 1000 wall exterior hinge left swing in
opening id=o_living at (4000,3700) width 900 wall partition
door id=d_bed at (6400,3000) width 800 wall partition hinge right swing out
door id=d_bath at (4600,4400) width 800 wall partition hinge left swing out
window at (0,2000) width 1500 wall exterior
window at (7000,1500) width 1200 wall exterior
window at (7000,5200) width 700 wall exterior
# Kitchen run along the north wall: sink · counter · stove · fridge (drawn as symbols).
furniture kitchen_sink at (300,250) size 800x600
furniture counter at (1200,250) size 600x600
furniture stove at (1950,250) size 600x600
furniture fridge at (2700,250) size 600x650
# Living + bedroom furniture, kept clear of the door swings.
furniture sofa at (350,4300) size 2000x900 label "Sofa"
furniture bed at (4300,300) size 1500x2000 label "Bed"
# Bathroom fixtures, kept clear of the door's entry path (the door swings out into
# the hall): shower in the far corner, basin against the partition, WC on the south
# wall — the left third of the room stays open so you can actually step inside.
# The WC is anchored to the room's south edge, so its rotation (180 — cistern to the
# south wall) is DERIVED; `flush` measures from that wall's inner face rather than the
# room rectangle's centerline edge, so the 200mm shell's half-thickness is never named.
furniture shower at (6000,5000) size 900x900 # against the E + S walls (corner)
furniture basin at (5200,4450) size 600x450 # back to the hall partition
furniture wc in r_bath anchor bottom flush size 400x700
# Dimension strings: room widths above, overall extents around. The reference
# (witness) points sit on the building's OUTER faces (x=-100/7100, y=-100/6100 for
# the 200mm shell) so the extension lines start at the wall face and read outward,
# never poking back into the building — while the spans still measure centerline to
# centerline (4000 · 3000 · 7000 · 6000).
dim (4000,-100)->(0,-100) offset 250 text "4000"
dim (7000,-100)->(4000,-100) offset 250 text "3000"
dim (0,6100)->(7000,6100) offset 500 text "7000"
dim (7100,6000)->(7100,0) offset 500 text "6000"
title {
project "Studio Apartment"
drawn_by "ArchCanvas"
date "2026-06-27"
}
}
Homes
Two-bedroom flat
5 rooms, 80 m², 3 doors, 4 windows on a 10 × 8 m footprint at scale 1:100 — a larger plan with a central corridor and several openings, placed absolutely at a real apartment's scale, with north reoriented.
# A two-bedroom apartment with a central corridor.
plan "Two-Bedroom Flat" {
units mm
grid 100
scale 1:100
north right
wall id=w_shell exterior thickness 250 { (0,0) (10000,0) (10000,8000) (0,8000) close }
wall id=w_kb partition thickness 100 { (4000,0) (4000,5000) }
wall id=w_bh partition thickness 100 { (4000,5000) (10000,5000) }
wall id=w_kb2 partition thickness 100 { (0,5000) (4000,5000) }
wall id=w_bath_w partition thickness 100 { (4000,5000) (4000,8000) }
wall id=w_hall partition thickness 100 { (7000,5000) (7000,8000) }
room id=r_kitchen at (0,0) size 4000x5000 label "Kitchen / Living"
room id=r_bed1 at (4000,0) size 6000x5000 label "Master Bedroom"
room id=r_bed2 at (0,5000) size 4000x3000 label "Bedroom 2"
room id=r_bath at (4000,5000) size 3000x3000 label "Bathroom"
room id=r_hall at (7000,5000) size 3000x3000 label "Hall"
door id=d_front at (8500,8000) width 1000 wall w_shell hinge left swing into r_hall
door at (4000,2500) width 900 wall partition hinge left swing in
door id=d_bed2 on w_kb2 at 2000 width 900 hinge right swing into r_bed2
door at (5500,5000) width 800 wall partition hinge left swing in
door at (7000,6500) width 900 wall partition hinge right swing in
window at (2000,0) width 2000 wall exterior
window at (8000,0) width 2400 wall exterior
window at (0,3000) width 1500 wall exterior
window at (10000,3000) width 1500 wall exterior
window id=w_bed2 at (2000,8000) width 1400 wall w_shell
# The master bed's head is already on the north wall (rotate 0). Bedroom 2's shares no
# wall with the corridor, so its head goes to the south wall instead.
furniture bed at (5000,300) size 1800x2100 label "Bed"
furniture bed at (300,5800) size 1400x2000 label "Bed" rotate 180
furniture wardrobe in r_bed1 anchor top-right flush size 1200x600
furniture wardrobe against wall w_bath_w offset 1500 in r_bed2
# Kitchen run along the west wall, clear of the window at (0,3000); the living half of
# the room takes a sofa and a small table.
furniture fridge against wall w_shell segment 3 offset 7500 in r_kitchen
furniture stove against wall w_shell segment 3 offset 6800 in r_kitchen
furniture kitchen_sink against wall w_shell segment 3 offset 6100 in r_kitchen
furniture sofa at (2850,600) size 850x600 label "Sofa" in r_kitchen
furniture table at (200,3200) size 700x500 label "Table" in r_kitchen
furniture shower against wall w_bath_w offset 2000 in r_bath
furniture wc against wall w_shell segment 2 offset 4500 in r_bath
furniture basin against wall w_shell segment 2 offset 3400 in r_bath
# The eaves: `w_shell` is the plan's one closed exterior ring, so no `wall` clause is
# needed to disambiguate it.
roof overhang 500
# Overall extents, outside face to outside face: `faces` pushes each endpoint out
# onto the wall it runs into, so the printed span is the true 10300 × 8300 the
# building measures — the 10000 × 8000 room grid plus half the shell at each end,
# and note the shell is 300 not 250 here because `grid 100` snaps the thickness.
# Exactly the arithmetic a hand-written `text` gets wrong (it used to say 10000).
dim faces (0,8000)->(10000,8000) offset 700
dim faces (0,0)->(0,8000) offset 700
title {
project "Two-Bedroom Flat"
drawn_by "ArchCanvas"
date "2026-06-25"
}
}
Attached (placement sugar)
A one-bedroom flat — 2 rooms, 28 m² — with no hand-computed coordinates for its openings or furniture. It exercises the placement sugar together: a strip laying rooms out end to end, doors and windows attached to a wall by position (on <wall> at <pos>, so they can never be reported "off wall"), a door that opens toward a named room (swing into), and furniture anchored inside a room (in <room> anchor … inset …).
# A one-bedroom flat authored with the v1.13 placement sugar — no hand-computed
# coordinates for openings or furniture. It exercises, together:
# • `strip` — a row of rooms laid out end to end
# • openings attached to a wall by position (`door|window … on <wall> at <pos>`)
# • a door that opens toward a named room (`swing into`) hinged at a wall end
# • furniture placed relative to a room (`in <room> anchor …`)
# Compiles clean and passes `arch lint`.
plan "Attached 1BR" {
units mm
grid 100
north up
# Living + bedroom laid left-to-right by a strip, sharing a 4 m depth.
strip right at (0,0) gap 0 height 4000 {
room id=r_living size 4000 label "Living" uses living
room id=r_bed size 3000 label "Bedroom" uses bedroom
}
# Exterior shell as four straight walls (each a clean start→end to attach onto)
# plus the partition between the two rooms.
wall id=w_north exterior thickness 200 { (0,0) (7000,0) }
wall id=w_south exterior thickness 200 { (0,4000) (7000,4000) }
wall id=w_west exterior thickness 200 { (0,0) (0,4000) }
wall id=w_east exterior thickness 200 { (7000,0) (7000,4000) }
wall id=w_part partition thickness 100 { (4000,0) (4000,4000) }
# Entrance 2000 mm along the south wall, hinged at that wall's start end and
# opening into the living room. The bedroom door on the partition opens inward.
door id=d_main on w_south at 2000 width 1000 hinge near start swing into r_living
door id=d_bed on w_part at 2000 width 900 swing into r_bed
# A window centred on each room's exterior wall.
window on w_west at 50% width 1400
window on w_east at 50% width 1200
# Furniture placed by anchor inside each room (never off a coordinate).
furniture sofa in r_living anchor top-left inset 300 size 2000x900 label "Sofa"
furniture bed in r_bed anchor top-right inset 300 size 1500x2000 label "Bed"
}
Furnished Flat (the drawn symbol catalogue)
7 rooms, 90.7 m², 6 doors, 6 windows on an 8.4 × 10.8 m footprint — and twenty-six of the thirty-two catalogued furniture kinds, which is what it is here for. Every other example draws a fixture or two in passing; this one exercises all five symbol domains at once: a fitted kitchen run with an island and a dashed overhead cabinet, a plumbed bathroom, two furnished bedrooms, a lounge and dining zone, and a utility room with a washer beside a dryer (the same box, told apart by the chords across its drum).
Two things to read in the source rather than the drawing. Not one piece carries a label — a drawn symbol ignores it, so the only text here comes from the rooms. And most carry no size either: against wall <id> … in <room> takes the catalogued footprint and derives the rotation from the wall, so the whole kitchen, bathroom and both beds are written without a hand-computed number. offset is where a piece's centre lands along the wall run, which is why a fitted run reads as a sequence of centres rather than corners. arch validate --strict is clean.
# The FURNITURE flagship — a two-bedroom flat, furnished with the whole symbol catalogue.
#
# Every other example draws a fixture or two in passing. This one is here for the drawn
# symbols themselves: thirty-six of the eighty-three catalogued kinds, across all five
# indoor domains (bath, kitchen/utility, bedroom, living/dining, office/misc). It is a
# two-bedroom flat rather than a showroom, so the ceiling on that number is the FLOOR
# AREA, not the catalogue: every piece here is somewhere a real one would go, and the
# linter is what says so -- `arch validate --strict` is clean, which means no piece
# floats off its wall, blocks a door, eats a clearance or narrows the only walk.
#
# Three things to read off the source rather than the picture:
#
# 1. NO LABELS. A drawn symbol ignores `label` — the word reaches `describe()` and
# nothing else — so the only text in this drawing comes from the rooms.
# 2. MOST PIECES CARRY NO SIZE. `against wall <id> … in <room>` takes the catalogued
# footprint (a fridge is 600 along the wall by 650 deep) and derives the rotation
# from the wall, so a fitted kitchen, a plumbed bathroom and a bed with its
# headboard on the right wall are written without a single hand-computed number.
# `offset` is where the piece's CENTRE lands along the wall run, so a kitchen run
# is written as a sequence of centres, not as corners.
# Only the free-standing pieces — the L-sofa, armchair, coffee table, dining table,
# island, desk, office chair, plant, rug, piano and lounger — declare a `size`,
# because with no wall there is nothing to tell `along` from `depth`.
# 3. THE RUG IS AN UNDERLAY. It is walked on rather than round, so the pieces standing
# on it raise no overlap warning and the circulation flood-fill crosses it. Rugs,
# alone among the symbols, are drawn unfilled — nothing they are written after can
# be hidden by them.
plan "Furnished Flat" {
units mm
grid 50
paper A3 portrait
scale 1:50
north up
dims auto overall
schedule rooms
wall id=w_north exterior thickness 200 { (0,0) (8400,0) }
wall id=w_east exterior thickness 200 { (8400,0) (8400,10800) }
wall id=w_south exterior thickness 200 { (0,10800) (8400,10800) }
wall id=w_west exterior thickness 200 { (0,0) (0,10800) }
wall id=w_kit partition thickness 100 { (5000,0) (5000,5600) }
wall id=w_util partition thickness 100 { (5000,3800) (8400,3800) }
wall id=w_hall_n partition thickness 100 { (0,5600) (8400,5600) }
wall id=w_hall_s partition thickness 100 { (0,6800) (8400,6800) }
wall id=w_bed partition thickness 100 { (3600,6800) (3600,10800) }
wall id=w_bath partition thickness 100 { (6400,6800) (6400,10800) }
room id=r_live at (0,0) size 5000x5600 label "Living / Dining" uses living
room id=r_kitchen at (5000,0) size 3400x3800 label "Kitchen" uses kitchen
room id=r_util at (5000,3800) size 3400x1800 label "Utility"
room id=r_hall at (0,5600) size 8400x1200 label "Hall" uses hall
room id=r_bed1 at (0,6800) size 3600x4000 label "Bedroom 1" uses bedroom
room id=r_bed2 at (3600,6800) size 2800x4000 label "Bedroom 2" uses bedroom
room id=r_bath at (6400,6800) size 2000x4000 label "Bath" uses bath
door id=d_front on w_east at 6200 width 900 hinge near start swing into r_hall
door id=d_live on w_hall_n at 900 width 900 hinge near start swing into r_live
door id=d_util on w_hall_n at 5700 width 800 hinge near start swing into r_util
door id=d_bed1 on w_hall_s at 700 width 800 hinge near start swing into r_bed1
door id=d_bed2 on w_hall_s at 5400 width 800 hinge near start swing into r_bed2
door id=d_bath on w_hall_s at 7000 width 800 hinge near start swing into r_bath
opening on w_kit at 1600 width 1200
window on w_north at 3300 width 1400
window on w_north at 6650 width 1200
window on w_west at 1800 width 1400
window on w_west at 4300 width 1200
window on w_south at 2600 width 1400
window on w_south at 4400 width 1400
# --- Living / Dining --------------------------------------------------------
# The rug is an UNDERLAY: the coffee table and the L-sofa stand on it, so that overlap
# is the arrangement rather than a collision, and the flood-fill walks straight over it.
# It is also the only symbol here drawn with no fill, which is why it cannot hide the
# sofa it is written after.
furniture rug at (200,600) size 2700x2400 in r_live
furniture tv_unit against wall w_north offset 1000 in r_live
furniture sofa_l at (300,1300) size 2400x1500 rotate 180 in r_live
furniture coffee_table at (900,700) size 1000x500 in r_live
furniture armchair at (3300,1100) size 900x850 rotate 90 in r_live
furniture sun_lounger at (1950,150) size 1400x700 rotate 90 in r_live
furniture plant at (4300,300) size 600x600 in r_live
# A piano carries NO catalogued footprint on purpose, so it cannot be written
# `against wall` — that form would derive a rotation from the wall and face the
# keyboard into it. Placed by hand, like it is on a real drawing.
furniture piano at (250,3200) size 1400x1400 in r_live
furniture dining_table at (2700,3300) size 2100x2000 in r_live
furniture radiator against wall w_west offset 4300 in r_live
# --- Kitchen: a fitted run of centres along the north wall -------------------
furniture counter against wall w_north offset 5650 size 1200x600 in r_kitchen
furniture kitchen_sink against wall w_north offset 6650 in r_kitchen
furniture stove against wall w_north offset 7350 in r_kitchen
furniture dishwasher against wall w_north offset 7950 in r_kitchen
furniture fridge against wall w_east offset 1000 in r_kitchen
furniture upper_cabinet against wall w_east offset 1800 in r_kitchen
furniture microwave against wall w_east offset 2600 in r_kitchen
furniture island at (5800,2000) size 1600x900 in r_kitchen
# --- Utility ----------------------------------------------------------------
furniture washer against wall w_east offset 4400 in r_util
furniture dryer against wall w_east offset 5000 in r_util
furniture laundry_sink against wall w_util offset 1300 in r_util
furniture water_heater against wall w_util offset 500 in r_util
# --- Bedroom 1 --------------------------------------------------------------
furniture double_bed against wall w_west offset 9100 in r_bed1
furniture nightstand against wall w_west offset 7925 in r_bed1
furniture nightstand against wall w_west offset 10275 in r_bed1
furniture wardrobe against wall w_hall_s offset 2600 in r_bed1
furniture vanity against wall w_bed offset 1700 in r_bed1
furniture dresser against wall w_bed offset 3250 in r_bed1
# --- Bedroom 2 --------------------------------------------------------------
furniture bed against wall w_bath offset 2950 in r_bed2
furniture desk against wall w_bed offset 1000 size 1200x700 in r_bed2
furniture office_chair at (4450,8200) size 600x600 in r_bed2
furniture bookshelf against wall w_bed offset 3350 in r_bed2
# --- Bath -------------------------------------------------------------------
furniture shower against wall w_bath offset 1350 in r_bath
furniture basin against wall w_east offset 7800 in r_bath
furniture wc against wall w_east offset 9200 in r_bath
furniture bathtub against wall w_south offset 7450 in r_bath
furniture bidet against wall w_east offset 8500 in r_bath
title { project "Furnished Flat" drawn_by "ArchLang" date "2026-08-26" }
}
Suburban Bungalow (orientation, and doors that are not hinged)
A single-storey house — 8 rooms, 102 m², 6 doors, 7 windows at scale 1:100 — on a plot that fronts the street on the south, in the southern hemisphere, and the whole layout follows from that one declared fact. site { street south hemisphere south } (site and orientation) draws nothing; it names five directions on arch describe --json --select site — street (S), back (N), equator_side (N), sunrise_side (E), sunset_side (W). Here back and equator_side are the same side, which is why the house turns away from the road: the living room and both bedrooms take the north facade, and the service band — entry, hall, bath, laundry, kitchen — is stacked along the street. Delete Bedroom 2's north window and arch lint says so, through the one advisory rule that reads a site, W_ROOM_NOT_EQUATOR_FACING. These are drafting heuristics for an aspect, not a daylight measurement: there is no sun model, no latitude and no date anywhere in ArchLang.
The other half is the door kinds. Three of the six doors are not hinged, each where a builder would actually put one: a sliding garden door onto the deck, a pocket door to the bath so no leaf lands in a 1500 mm corridor, and a bifold to the laundry. slide right on the pocket is load-bearing, not cosmetic — it aims the panel at the 2200 mm of solid wall between its jamb and the laundry door, where slide left would give it 500 mm against the 850 mm it needs and raise W_POCKET_RUN. A non-hinged leaf sweeps nothing, so only the two bedroom doors carry a swing arc, and describe().doors[].kind appears on exactly the three that are not the default.
# A single-storey suburban bungalow — the ORIENTATION-AND-OPENINGS example (v1.25).
#
# The plot fronts the street on the SOUTH and the garden on the NORTH, in the southern
# hemisphere. That one fact decides the whole layout, so the plan states it:
#
# site { street south hemisphere south }
#
# `site` draws nothing. It names five directions on `arch describe --json --select site`:
# `street` (S), `back` (N — the garden), `equator_side` (N, because south of the equator
# the equator lies northward), `sunrise_side` (E) and `sunset_side` (W). Here `back` and
# `equator_side` are the SAME side, which is exactly why the house turns away from the
# road: the living room and both bedrooms take the north facade, and the service band —
# entry, hall, bath, laundry, kitchen — is stacked along the street. One advisory lint
# rule reads it, `W_ROOM_NOT_EQUATOR_FACING`; leave a bedroom with nothing but its east
# window and it will say so.
#
# These are DRAFTING HEURISTICS FOR AN ASPECT, not a daylight measurement. ArchLang has
# no sun model, no latitude and no date, so `equator_side` says which way a window looks
# and nothing whatever about how much light comes through it.
#
# The other half of the plan is the door KINDS. A leaf is not always hinged, and the
# three non-default kinds here are each where a builder would actually put one:
#
# sliding the garden door — the full-height slider onto the deck
# pocket the bath, off a 1500 mm corridor: no leaf in the hall, none in the bath
# bifold the laundry, folding into its own opening rather than over the machines
#
# A non-hinged leaf sweeps nothing, so `doorSwing()` returns null for all three and
# `W_SWING_OBSTRUCTED` cannot apply to them — only the two bedroom doors, which take the
# default `hinged` by writing no kind at all, carry a swing arc.
plan "Bungalow" {
units mm
# `grid 50` because every partition here is 100 thick and the plan is set out in
# half-metres. It used to be a WORKAROUND as well: a fixture placed `flush` against a
# 100 partition lands on a half-thickness (…50) coordinate, and the resolver used to
# grid-snap that DERIVED coordinate like an authored one, so `grid 100` rounded it back
# INTO the wall and raised `W_FURNITURE_WALL_COLLISION` on a correct plan. A derived
# coordinate is no longer snapped, so the grid is free again — these numbers are
# unchanged, and so is every byte this file draws.
grid 50
paper A3 landscape
scale 1:100
north up
dims auto all
schedule rooms
site { street south hemisphere south }
# ---- shell + structure ----------------------------------------------------
# `w_spine` runs the full width and divides the garden band from everything behind it;
# `w_service` does the same for the street band. Both are named because the doors below
# are placed `on` them by RUN DISTANCE from the wall's first point, and because
# `W_POCKET_RUN` measures the panel's cavity along one named segment.
wall id=shell exterior thickness 200 { (0,0) (12000,0) (12000,8500) (0,8500) close }
wall id=w_bed1 partition thickness 100 { (6000,0) (6000,4800) }
wall id=w_bed2 partition thickness 100 { (9000,0) (9000,4800) }
wall id=w_spine partition thickness 100 { (0,4800) (12000,4800) }
wall id=w_kitchen partition thickness 100 { (3000,4800) (3000,8500) }
wall id=w_service partition thickness 100 { (3000,6300) (12000,6300) }
wall id=w_bath partition thickness 100 { (5800,6300) (5800,8500) }
wall id=w_laundry partition thickness 100 { (8000,6300) (8000,8500) }
# ---- rooms ----------------------------------------------------------------
# Garden band (north): the rooms people sit and sleep in, every one of them on the
# equator-facing facade. Street band (south): the rooms that do not need it.
room id=r_living at (0,0) size 6000x4800 label "Living / Dining" uses living dining
room id=r_bed1 at (6000,0) size 3000x4800 label "Bedroom 1" uses bedroom
room id=r_bed2 at (9000,0) size 3000x4800 label "Bedroom 2" uses bedroom
room id=r_kitchen at (0,4800) size 3000x3700 label "Kitchen" uses kitchen
room id=r_hall at (3000,4800) size 9000x1500 label "Hall" uses hall circulation
room id=r_bath at (3000,6300) size 2800x2200 label "Bath" uses bath
room id=r_laundry at (5800,6300) size 2200x2200 label "Laundry" uses utility
room id=r_entry at (8000,6300) size 4000x2200 label "Entry" uses entry
# ---- doors: one kind per situation ----------------------------------------
# The front door is on the street facade, and it is the only exterior hinged leaf.
door id=d_front at (10000,8500) width 1000 wall shell hinge left swing in
# The garden door. A slider is what a deck opening actually is, and it is why the
# living room can open this far to the north without a leaf eating the floor.
# `slide left` runs the panel back toward the wall's start — onto the solid pier
# between this opening and the window, not across the glass.
door id=d_garden sliding at (4800,0) width 2000 wall shell slide left
# Bedrooms keep the default: `hinged` is what you get by writing no kind at all, and
# these two are the only doors in the plan with a swing arc to obstruct.
door id=d_bed1 on w_spine at 7500 width 800 hinge left swing in
door id=d_bed2 on w_spine at 10500 width 800 hinge right swing in
# The bath. A hinged leaf here has nowhere good to go: outward it blocks a 1500 mm
# corridor, inward it fouls the basin. A pocket door answers both, and `slide` is not
# cosmetic here — `w_service` is walked west to east, so `slide right` drives the panel
# into the 2200 mm of solid wall between this jamb and the laundry door. `slide left`
# would aim it at the corner instead, where the run is 500 mm against the 850 mm an
# 800 mm panel needs, and `W_POCKET_RUN` would report it 350 mm short.
door id=d_bath pocket on w_service at 900 width 800 slide right
# The laundry, one room further along the same wall. A bifold folds into its own
# opening, so it swings neither into the corridor nor over the machines.
door id=d_laundry bifold on w_service at 3900 width 800
# Cased openings: circulation needs no leaf.
opening id=o_kitchen at (1500,4800) width 2400 wall w_spine
opening id=o_living at (4500,4800) width 1400 wall w_spine
opening id=o_pantry at (3000,5550) width 900 wall w_kitchen
opening id=o_entry at (10000,6300) width 1200 wall w_service
# ---- windows --------------------------------------------------------------
# North first: every habitable room gets the equator-facing aspect, which is what keeps
# `W_ROOM_NOT_EQUATOR_FACING` quiet. Then the ends, then the street side.
window id=n_living at (900,0) width 1400 wall shell
window id=n_bed1 at (7500,0) width 1600 wall shell
window id=n_bed2 at (10500,0) width 1600 wall shell
window id=e_bed2 at (12000,2400) width 1200 wall shell # sunrise side as well as north
window id=e_hall at (12000,5550) width 900 wall shell # light to the corridor's end
window id=w_sink at (0,6600) width 1200 wall shell # sunset side, over the sink
window id=s_bath at (3600,8500) width 600 wall shell
# ---- fixtures -------------------------------------------------------------
# No fixture here names an absolute coordinate against a wall, and none writes a
# `rotate`. The kitchen run rides the shell's west segment, so `offset` is a distance
# ALONG that segment (measured from its first point, (0,8500)) and both the position
# and the quarter-turn are derived from the wall; the size comes from the catalogue.
# The sanitary fixtures are anchored to their room instead, where `flush` measures
# from the backing wall's inner FACE, so no half-thickness is written down by hand.
furniture kitchen_sink against wall shell segment 3 offset 1900 in r_kitchen # under the window
furniture stove against wall shell segment 3 offset 1100 in r_kitchen
furniture fridge against wall shell segment 3 offset 500 in r_kitchen
furniture basin in r_bath anchor left flush size 450x600
furniture wc in r_bath anchor bottom flush size 400x700
furniture shower in r_bath anchor right flush size 900x900
furniture counter in r_laundry anchor bottom flush size 1400x600
furniture sofa at (600,2600) size 2200x900 label "Sofa"
furniture table at (3600,1800) size 1400x900 label "Table"
furniture bed at (6200,200) size 1500x2000 label "Bed"
furniture bed at (9200,200) size 1500x2000 label "Bed"
# ---- the eaves ------------------------------------------------------------
# A bungalow is mostly roof, and on a hot plot it is the eave that shades the north
# glazing rather than any glass. One line draws it: the outline is DERIVED by pushing
# `shell`'s ring out by half its thickness plus 600, mitred at each corner, so it moves
# if the walls do and there is no second set of coordinates to keep in step. `shell` is
# the only closed `exterior` wall here, so it needs no naming — write
# `roof overhang 600 wall shell` if a second one is ever added, or the plan gets
# `E_ROOF_AMBIGUOUS` rather than a guess.
#
# It draws and nothing more: no `describe()` key, no lint rule, no schedule row. What
# it does change is the PAGE — the drawing extent now includes the eaves, so the sheet
# grows to hold them.
roof overhang 600
title {
project "Suburban Bungalow"
drawn_by "ArchCanvas"
date "2026-08-13"
}
}
Courtyard House
A 16 × 11.5 m single-storey house whose rooms ring an open middle — 11 rooms, 163 m², 10 doors, 15 windows at scale 1:100.
The first thing to understand about the courtyard is that it is not a room. Nothing is declared inside it: it is the hole the building wraps around, open to the sky, and calling it a room would put 21 m² of weather into the floor schedule. It is enclosed by a single three-segment partition, and everything that reads the plan — the schedule, describe().totals, the circulation flood-fill — simply sees no floor there. The courtyard is also the shape that breaks the naive way of answering which way does this window look? The window on the courtyard's west wall has the gallery's floor to its west and open air to its east, so it looks east, not west; ArchLang answers by probing one wall thickness off each face of the window's own host segment and taking the side no room occupies, which is exact here and on a sloped facade alike. Read it back with arch describe --json --select windows.
The wings are zones — living 67.75 m² over 4 rooms, private 65.5 m² over 4, service 29.75 m² over 3. A zone has zero geometry: delete the wrappers and the drawing is byte-identical. Membership is declared, never inferred from where a room happens to sit, which is why the schedule can carry a subtotal per wing that no amount of looking at the drawing could have derived. The gallery is one polygon room — a U wrapping three sides of the courtyard, so the schedule lists one corridor rather than three — and because a U's centroid falls outside it, its name is pinned by hand in the south arm.
# A 16.3 x 11.8 m single-storey house whose rooms ring an open courtyard.
#
# The courtyard is the whole point of the plan, and the first thing to understand about
# it is that IT IS NOT A ROOM. Nothing is declared at (5000,3500)-(11000,7000): it is
# the hole the building is wrapped around, open to the sky, and calling it a `room` with
# a `uses` would put 21 m2 of weather into the floor schedule. What encloses it is a
# single three-segment partition, `w_court`, and everything that reads the plan — the
# schedule, `describe().totals`, the circulation flood-fill — simply sees no floor there.
#
# THE FACING RULE. A courtyard is the shape that breaks the naive way of answering
# "which way does this window look?". Take `w_court_w`, the window on the courtyard's
# WEST wall. That wall is west of the courtyard, so a rule that compared the window with
# the middle of the building would call it west-facing. It is not: the gallery's floor is
# on the west side of that wall and the open air is on the east, so the window looks
# EAST. ArchLang answers it by probing one wall thickness off each face of the window's
# own host segment and taking the side no room occupies, which is exact on a courtyard
# and on a sloped facade alike. Read it back with:
#
# arch describe examples/courtyard-house.arch --json --select windows
#
# and `w_court_w` reports `"facing": "east"`, `w_court_e` reports `"facing": "west"` —
# each looking ACROSS the courtyard, which is what someone standing at them sees.
#
# ZONES. `zone living { … }`, `zone private { … }` and `zone service { … }` group the
# room statements into wings. A zone has ZERO geometry: every statement inside resolves
# exactly as if the wrapper were deleted, same coordinates, same ids, byte-identical
# drawing. Membership is DECLARED, never inferred from where a room happens to sit —
# which is why the schedule below can carry a subtotal per wing that no amount of
# looking at the drawing could have derived. `arch describe --json --select zones` rolls
# the areas up; `--zone private` reads one wing on its own.
#
# The gallery is one POLYGON room, not three rectangles: a U wrapping the courtyard on
# west, south and east. Written as three rooms it would need three openings between
# pieces of the same corridor, and the schedule would list a corridor three times. Its
# centroid falls in the courtyard — a U is the shape whose average point is outside it —
# so the label is placed by hand in the south arm.
#
# Also here: `left-of` and `above`, the two relational directions no other example uses;
# `sliding` doors where a room opens onto the courtyard (a leaf that swings 900 mm into
# a 1500 mm gallery is a leaf in the way); and one `pocket` door for the same reason.
plan "Courtyard House" {
units mm
# `grid 50`: partitions are 100 thick, so a `flush` fixture lands on a ...50 face.
grid 50
paper A3 landscape
scale 1:100
north up
dims auto all
schedule rooms
legend
# The plot fronts the street on the WEST. `site` draws nothing; it names five
# directions on `describe --json --select site` — street (W), back (E), equator_side
# (S, north of the equator), sunrise_side (E), sunset_side (W). They are a DRAFTING
# HEURISTIC for an aspect: ArchLang has no sun model, no latitude and no date.
site { street west hemisphere north }
# ---- shell, bands and the courtyard wall ----------------------------------
wall id=shell exterior thickness 300 { (0,0) (16000,0) (16000,11500) (0,11500) close }
# Two full-width partitions do most of the work: one behind the north band of rooms,
# one in front of the south band of bedrooms. Each is a single wall because the line
# is continuous even though what it separates changes along it.
wall id=w_band_n partition thickness 200 { (0,3500) (16000,3500) }
wall id=w_band_s partition thickness 200 { (0,8500) (16000,8500) }
# The courtyard itself: one wall, three segments, mitred at both corners.
wall id=w_court partition thickness 200 { (5000,3500) (5000,7000) (11000,7000) (11000,3500) }
# 150, not 100: a pocket door needs a partition thick enough to swallow its panel.
wall id=w_gal_w partition thickness 150 { (3500,3500) (3500,8500) }
wall id=w_gal_e partition thickness 100 { (12500,3500) (12500,8500) }
wall id=w_entry partition thickness 100 { (3500,0) (3500,3500) }
wall id=w_dining partition thickness 100 { (9000,0) (9000,3500) }
wall id=w_kitch partition thickness 100 { (12500,0) (12500,3500) }
wall id=w_bath partition thickness 100 { (12500,6000) (16000,6000) }
wall id=w_bed1 partition thickness 100 { (5500,8500) (5500,11500) }
wall id=w_bed2 partition thickness 100 { (11000,8500) (11000,11500) }
# ---- rooms, grouped into wings by `zone` ----------------------------------
zone living "Living wing" {
room id=r_living at (3500,0) size 5500x3500 label "Living" uses living
# `left-of` places the entry against the living room's west face and `align top`
# lines their north edges up — no second coordinate is written, and moving the
# living room moves the entry with it.
room id=r_entry left-of r_living align top size 3500x3500 label "Entry" uses entry hall
room id=r_dining at (9000,0) size 3500x3500 label "Dining" uses dining
# The gallery: a U around the courtyard. Its centroid is in the open air, so the
# label point is given explicitly (otherwise `W_ROOM_LABEL_OUTSIDE`).
room id=r_gallery polygon
(3500,3500) (5000,3500) (5000,7000) (11000,7000) (11000,3500)
(12500,3500) (12500,8500) (3500,8500)
label "Gallery" at (8000,7750) uses circulation hall
}
zone private "Private wing" {
room id=r_bed1 at (0,8500) size 5500x3000 label "Bedroom 1" uses bedroom
room id=r_bed2 at (5500,8500) size 5500x3000 label "Bedroom 2" uses bedroom
room id=r_bed3 at (11000,8500) size 5000x3000 label "Bedroom 3" uses bedroom
# `above` is the other new direction: the study sits directly on top of bedroom 1,
# its own depth up from that room's north edge, west edges aligned.
room id=r_study above r_bed1 align left size 3500x5000 label "Study" uses office
}
zone service "Service wing" {
room id=r_kitchen at (12500,0) size 3500x3500 label "Kitchen" uses kitchen
room id=r_bath at (12500,3500) size 3500x2500 label "Bathroom" uses bath
room id=r_util at (12500,6000) size 3500x2500 label "Utility" uses utility
}
# ---- doors ----------------------------------------------------------------
# `swing in` and not `swing into r_entry`: a relationally-placed room is deliberately
# NOT a `swing into` target, because its rectangle is DERIVED — the compiler will not
# resolve an authored clause against a position it computed for you. On an exterior
# wall the explicit word is the plain one anyway: `in` is the shell's inward face.
door id=d_front at (0,1750) width 1000 wall shell hinge left swing in
# Onto the courtyard. A slider is what a full-height garden opening actually is, and
# a hinged leaf here would sweep either the gallery or the courtyard threshold.
door id=d_court_l sliding on w_band_n at 6000 width 1800 slide left
door id=d_court_g sliding on w_court at 6500 width 2000 slide right
# The study, off a gallery only 1350 mm wide between faces. A pocket keeps both sides
# clear; `slide right` drives the panel south into 2100 mm of blank wall (it needs
# 850 — its own 800 plus a jamb), where `slide left` would aim it at the corner.
door id=d_study pocket on w_gal_w at 2500 width 800 slide right
door id=d_bath on w_gal_e at 750 width 800 swing into r_bath
door id=d_util on w_gal_e at 4000 width 800 swing into r_util
door id=d_kitch on w_kitch at 1750 width 900 swing into r_kitchen
door id=d_bed1 on w_band_s at 4500 width 900 swing into r_bed1
door id=d_bed2 on w_band_s at 8000 width 900 swing into r_bed2
door id=d_bed3 on w_band_s at 11750 width 900 swing into r_bed3
# Cased openings: the public rooms read into one another with no leaf at all.
opening id=o_entry at (3500,1750) width 1500 wall w_entry
opening id=o_dining at (9000,1750) width 1600 wall w_dining
opening id=o_gal_w at (4250,3500) width 1200 wall w_band_n
opening id=o_gal_e at (11750,3500) width 1200 wall w_band_n
# ---- windows: outer facades first -----------------------------------------
window id=n_living at (6000,0) width 2000 wall shell
window id=n_dining at (10750,0) width 1600 wall shell
window id=n_kitchen at (14250,0) width 1400 wall shell
window id=e_kitchen at (16000,1750) width 900 wall shell
window id=e_bath at (16000,4750) width 600 wall shell
window id=e_util at (16000,7250) width 600 wall shell
window id=s_bed1 at (2750,11500) width 1600 wall shell
window id=s_bed2 at (8250,11500) width 1600 wall shell
window id=s_bed3 at (13500,11500) width 1600 wall shell
window id=w_study at (0,6000) width 1400 wall shell
window id=w_entry_l at (0,2950) width 800 wall shell
# ---- ...then the courtyard walls ------------------------------------------
# These three are the facing demonstration. `w_court_w` is hosted on the courtyard's
# WEST wall and faces EAST; `w_court_e` is on its east wall and faces WEST; the dining
# room's and the living room's courtyard windows are on the band wall and face SOUTH,
# which is the same equator side the bedrooms take from the outer facade.
window id=w_court_l at (8000,3500) width 1200 wall w_band_n
window id=w_court_w at (5000,5250) width 1200 wall w_court
window id=w_court_e at (11000,5250) width 1200 wall w_court
window id=w_court_d at (10000,3500) width 1600 wall w_band_n
# ---- furniture ------------------------------------------------------------
furniture sofa in r_living anchor top flush size 2400x900 label "Sofa"
furniture table in r_living centered size 1600x900 label "Table"
furniture table in r_dining centered size 1600x900 label "Dining table"
# The kitchen run rides the shell's north segment; `offset` is the distance of each
# piece's CENTRE along it from (0,0), and the footprints come from the catalogue.
furniture kitchen_sink against wall shell segment 0 offset 13100 in r_kitchen
furniture stove against wall shell segment 0 offset 13800 in r_kitchen
furniture counter against wall shell segment 0 offset 14400 in r_kitchen
furniture fridge against wall shell segment 0 offset 15000 in r_kitchen
furniture bathtub in r_bath anchor top-right flush size 1700x700
furniture wc in r_bath anchor bottom-left flush size 400x700
furniture basin in r_bath anchor bottom-right flush size 600x450
furniture counter in r_util anchor top flush size 1800x600
# 900 x 600 of plumbed appliance bay, flush to the east wall. It is drawn 900 wide
# because a label is drawn INSIDE its own footprint and the renderer has no metrics
# to shrink one with: at 1:100 the word "Washer" is 825 mm long on the plan.
# `rotate 90` puts the machine's BACK — where its supply and waste run — on the east
# wall it stands against. `washer` is catalogued as a wall-requiring fixture as of
# v1.28, so leaving it at rotate 0 reads as an appliance plumbed into open floor
# (W_FIXTURE_BACK_TO_ROOM). The footprint is square-ish and the piece is a plain
# rectangle, so nothing in the drawing moves.
furniture washer at (14950,7300) size 900x600 label "Washer" rotate 90
# The study is placed relationally, so its pieces take absolute points rather than
# anchors — the room's own rectangle is derived, and hanging furniture off a derived
# edge would hide which of the two the author actually meant.
furniture desk at (300,3700) size 1600x700 label "Desk"
furniture bookcase at (1500,8000) size 1800x400 label "Bookcase" rotate 180
furniture bench at (150,3000) size 1200x400 label "Bench"
furniture bed in r_bed1 anchor bottom-left flush size 1500x2000 label "Double"
furniture wardrobe at (4000,11000) size 1400x350 label "Wardrobe" rotate 180
furniture bed in r_bed2 anchor bottom-right flush size 1500x2000 label "Double"
furniture bed in r_bed3 anchor bottom-right flush size 1500x2000 label "Double"
# The one piece with no `in <room>` at all — because there is no room to name. A
# planter standing in the courtyard is furniture in the open air, and every rule that
# would ask which room it belongs to simply does not apply to it.
furniture planter at (7250,4750) size 1500x1500 label "Planter"
# ---- the eaves --------------------------------------------------------------
# `shell` is the plan's one closed exterior wall — a plain rectangle. The courtyard
# is cut by an INTERIOR partition, `w_court`, so it never enters this offset at all;
# the concave gallery that wraps it is a room polygon, not a wall, and `roof` only
# ever reads the wall ring.
roof overhang 600
title {
project "Courtyard House"
drawn_by "ArchLang"
date "2026-08-16"
}
}
Townhouse (three storeys)
A 5.5 × 11 m terrace house written as three level blocks, paper A3 portrait at scale 1:50. Each storey is 4 rooms and 55.64 m² and becomes its own page — ground (Hall, Living, Cloakroom, Kitchen/Dining), first (Landing, Bedroom 1, Bathroom, Bedroom 2), second (Landing, Study, Shower room, Main bedroom). arch compile writes townhouse.L1.svg, townhouse.L2.svg and townhouse.L3.svg; the widget below shows page
- Everything outside the blocks —
units,grid,paper,scale,dims auto— is stated once, because one building is issued on one sheet at one scale.
The stair is one object written three times. Ids are unique within a level, so the same id=st on all three storeys is not a clash — it is the declaration that these are one shaft, and it is what makes the upper floors legal: they have no front door of their own, and lint still reads the first-floor landing as reached from outside because describe().vertical connects it to the storey below. Change one storey's id and you get W_STAIR_UNMATCHED rather than a silently disconnected floor. dir is not decoration either: the flight runs along its footprint's long axis and dir up is entered at that axis's larger-coordinate end, which is also what the navigation grid reads — a stair obstructs circulation everywhere except its entry edge, and that is why the ground-floor flight stops 1.15 m short of the back wall. One flight-length further south, the only route from the front door to the kitchen measured 300 mm on the nav grid against a 700 minimum, and lint refused the plan with W_PATH_TOO_NARROW.
# A 5.5 x 11 m three-storey terrace house — one drawing per storey, from `level` blocks.
#
# A terrace house is the building type that only makes sense in section, and `level` is
# how a plan says so. Each block is an ordinary plan body and becomes its own page:
# `arch compile` writes townhouse.L1.svg, townhouse.L2.svg, townhouse.L3.svg, and
# `describe --json` grows a `levels[]`. Everything outside the blocks — `units`, `grid`,
# `paper`, `scale`, `dims auto` — is stated once and applies to every storey, because one
# building is issued on one sheet at one scale.
#
# THE STAIR IS ONE OBJECT WRITTEN THREE TIMES. Ids are unique WITHIN a level, so the same
# `id=st` on all three storeys is not a clash — it is the declaration that these are one
# shaft. That single fact is what makes the upper floors legal: they have no front door
# of their own, and `lint` still reads the first-floor landing as reached from outside,
# because `describe().vertical` connects it to the storey below. Change one storey's id
# and you get `W_STAIR_UNMATCHED` rather than a silently disconnected floor.
#
# `dir` is declared per storey and is not decoration: the flight runs along its footprint's
# LONG axis, and `dir up` is entered at that axis's larger-coordinate end. Here the long
# axis is y, so the ground-floor flight is entered from the back of the hall and climbs
# toward the street; the same footprint on the top floor carries `dir down`, entered at
# the opposite end. That is also what the nav grid reads — a stair obstructs circulation
# everywhere except its entry edge, which is why the flight stops 1.15 m short of the back
# wall. That strip is the only route from the front door to the kitchen, and one flight
# length further south it measured 350 mm of built depth — 300 on the nav grid, against a
# 700 minimum, so `lint` refused the plan with `W_PATH_TOO_NARROW`. Moving the flight is
# the fix, not widening a door: nothing on the route is a named element to widen.
#
# WHY THE PARTITIONS DON'T MOVE. The spine wall at x=3200, the wet-room wall at y=4600 and
# the cross wall at y=7000 stand on all three storeys in the same place, so the WC, the
# bathroom and the shower room sit one above the other on a single stack. That is not
# copy-paste: in a terrace the spine carries the floors and the drain fixes the wet rooms,
# so what a storey is free to change is only what its rooms are called and what is in them.
#
# Two door kinds earn their place rather than illustrate themselves. The ground-floor WC
# opens off a hall 1.75 m wide with a stair in it, so its leaf is a `pocket`: it takes no
# corridor and none of the 7.7 m2 cloakroom, and `slide left` drives the panel north into
# 2.65 m of blank spine wall (it needs 850 — its own 800 plus a jamb). The garden door is
# a `sliding` leaf, which is what a patio door actually is. A hinged one swinging in would
# cross the dining table; swung out it would sweep the paving — and the dimension chain a
# builder reads the back elevation from. No non-hinged leaf sweeps anything: `doorSwing()`
# returns null for every kind but `hinged`, so `W_SWING_OBSTRUCTED` cannot even apply to
# it. A `sliding` door is drawn as a BYPASS pair — two leaves on two tracks inside the
# reveal, so it needs no blank wall to park on at all — and `slide` only says which of the
# two is the fixed one: `right` is measured along the host wall's traversal, and the shell's
# south run is walked east to west, so the western leaf is fixed and the eastern one travels.
plan "Townhouse" {
units mm
# `grid 50`: the spine partition is 200 and the cross walls 100, so every `flush`
# fixture lands on a ...50 or ...00 face — coordinates this grid can express exactly.
grid 50
paper A3 portrait
scale 1:50
north up
dims auto all
# Outer faces 5500 x 11000; 4900 clear between the party walls. A terrace is blind on
# both flanks, so every window in the house is on the front or the back — which is the
# single fact that shapes the plan: the deep middle of each floor takes the stair and the
# wet room, and the two ends take the rooms people sit in.
let W = 5200 # shell centerlines
let D = 10700
let T = 300 # exterior thickness
let SPINE = 3200 # the loadbearing spine, front to mid-plan
let WET = 4600 # the wet-room wall — the same line on all three storeys
let BACK = 7000 # the cross wall in front of the back room
level 1 "Ground floor" {
wall id=shell exterior thickness T { (0,0) (W,0) (W,D) (0,D) close }
wall id=spine partition thickness 200 { (SPINE,0) (SPINE,BACK) }
wall id=w_wet partition thickness 100 { (0,WET) (SPINE,WET) }
wall id=w_back partition thickness 100 { (0,BACK) (W,BACK) }
room id=r_hall at (SPINE,0) size 2000x7000 label "Hall" uses hall entry circulation
room id=r_living at (0,0) size 3200x4600 label "Living" uses living
room id=r_wc at (0,WET) size 3200x2400 label "Cloakroom" uses wc
room id=r_kitchen at (0,BACK) size 5200x3700 label "Kitchen/Dining" uses kitchen dining
door id=d_front at (4200,0) width 1000 wall shell hinge left swing into r_hall
door id=d_living on spine at 2300 width 900 swing into r_living
# The pocket. `slide left` runs the panel back toward the spine's first point,
# (3200,0) — north, into solid wall — not south toward the corner 800 mm away.
door id=d_wc pocket on spine at 5800 width 800 slide left
# The patio slider: two bypass leaves in one 1.2 m reveal, nothing swept, nothing
# parked on the wall. `slide right` is west here, so the western leaf is the fixed one.
door id=d_back sliding at (4200,D) width 1200 wall shell slide right
opening id=o_kitchen at (4200,BACK) width 1200 wall w_back
window id=n_living at (1600,0) width 1600 wall shell
window id=s_kitchen at (1400,D) width 1600 wall shell
furniture media in r_living anchor top-left flush size 1200x400 label "Media"
furniture sofa in r_living anchor bottom flush size 2000x850 label "Sofa"
furniture wc in r_wc anchor bottom-left flush size 400x700
furniture basin in r_wc anchor bottom-right flush size 600x450
# The kitchen run rides the shell's WEST segment (index 3, walked from (0,10700)
# northward), so `offset` is a distance along that segment and both the position and
# the quarter-turn come from the wall; the footprints come from the catalogue.
furniture kitchen_sink against wall shell segment 3 offset 1200 in r_kitchen
furniture stove against wall shell segment 3 offset 2100 in r_kitchen
furniture fridge against wall shell segment 3 offset 2900 in r_kitchen
furniture table in r_kitchen centered size 1400x900 label "Table"
stair id=st at (4150,2600) size 900x3200 dir up
}
level 2 "First floor" {
wall id=shell exterior thickness T { (0,0) (W,0) (W,D) (0,D) close }
wall id=spine partition thickness 200 { (SPINE,0) (SPINE,BACK) }
wall id=w_wet partition thickness 100 { (0,WET) (SPINE,WET) }
wall id=w_back partition thickness 100 { (0,BACK) (W,BACK) }
room id=r_landing at (SPINE,0) size 2000x7000 label "Landing" uses circulation hall
room id=r_bed1 at (0,0) size 3200x4600 label "Bedroom 1" uses bedroom
room id=r_bath at (0,WET) size 3200x2400 label "Bathroom" uses bath
room id=r_bed2 at (0,BACK) size 5200x3700 label "Bedroom 2" uses bedroom
# No exterior door on this floor. The stair below carries the same id, so the access
# graph reaches this landing through the shaft.
door id=d_bed1 on spine at 1800 width 800 swing into r_bed1
door id=d_bath on spine at 5200 width 800 swing into r_bath
door id=d_bed2 on w_back at 4200 width 800 swing into r_bed2
window id=n_bed1 at (1600,0) width 1600 wall shell
window id=n_landing at (4200,0) width 1200 wall shell
window id=s_bed2 at (2600,D) width 1800 wall shell
furniture bed in r_bed1 anchor bottom-left flush size 1400x2000 label "Double"
furniture wardrobe in r_bed1 anchor bottom-right flush size 1200x600 label "Wardrobe"
# The three pieces go round the three walls the door is not on, so the leaf opens onto
# bare floor: bath under the back wall, WC beside it on the spine, basin by the window.
furniture bathtub in r_bath anchor bottom-left flush size 1700x700
furniture wc in r_bath anchor bottom-right flush size 400x700
furniture basin in r_bath anchor top-left flush size 600x450
furniture bed in r_bed2 anchor bottom-left flush size 1500x2000 label "Double"
furniture wardrobe in r_bed2 anchor top-left flush size 1400x600 label "Wardrobe"
furniture chest at (4550,9200) size 500x1000 label "Chest" in r_bed2
stair id=st at (4150,2600) size 900x3200 dir up
}
level 3 "Second floor" {
wall id=shell exterior thickness T { (0,0) (W,0) (W,D) (0,D) close }
wall id=spine partition thickness 200 { (SPINE,0) (SPINE,BACK) }
wall id=w_wet partition thickness 100 { (0,WET) (SPINE,WET) }
wall id=w_back partition thickness 100 { (0,BACK) (W,BACK) }
room id=r_landing at (SPINE,0) size 2000x7000 label "Landing" uses circulation hall
room id=r_study at (0,0) size 3200x4600 label "Study" uses office
room id=r_bath at (0,WET) size 3200x2400 label "Shower room" uses bath
room id=r_master at (0,BACK) size 5200x3700 label "Main bedroom" uses bedroom
door id=d_study on spine at 1800 width 800 swing into r_study
door id=d_bath on spine at 5200 width 800 swing into r_bath
door id=d_master on w_back at 4200 width 900 swing into r_master
window id=n_study at (1600,0) width 1600 wall shell
window id=n_landing at (4200,0) width 1200 wall shell
window id=s_master at (2600,D) width 1800 wall shell
furniture desk in r_study anchor top-left flush size 1600x700 label "Desk"
furniture bookcase in r_study anchor bottom-left flush size 1800x400 label "Bookcase"
# The same three walls as the bathroom below, and a shower where the bath is — the
# drain, the vent and the door all stay where the storey under them put them.
furniture shower in r_bath anchor bottom-left flush size 900x900
furniture wc in r_bath anchor bottom-right flush size 400x700
furniture basin in r_bath anchor top-left flush size 600x450
furniture bed in r_master anchor bottom-left flush size 1600x2100 label "Double"
furniture wardrobe in r_master anchor top-left flush size 1800x600 label "Wardrobe"
furniture chest at (4550,8500) size 500x1000 label "Chest" in r_master
stair id=st at (4150,2600) size 900x3200 dir down
# The eaves, on the top storey only — `roof` is per-level like everything else
# here, and this level's `shell` is the plan's one closed exterior ring on THIS
# page, so no `wall` clause is needed.
roof overhang 400
}
title {
project "Three-storey Terrace"
drawn_by "ArchLang"
date "2026-08-16"
}
}
Two-storey house
A compact house written as two level blocks — 3 rooms and 56 m² on the ground floor, paper A3 at scale 1:50. It compiles to two sheets (two-storey.L1.svg, two-storey.L2.svg), each with its own dimension chains and a title block stamped LEVEL 1 — Ground floor / LEVEL 2 — First floor. The settings and the lets sit outside the levels and apply to both: one building, one sheet spec, one scale. It is the smaller sibling of the townhouse above — the two-page version of the same idea, without the stack of wet rooms.
# A compact two-storey house — one drawing per storey, from `level` blocks.
#
# Settings and the plan-global `let`s live OUTSIDE the levels and apply to every one of
# them: one building is issued on one sheet at one scale. Each level's body is an ordinary
# plan body, and ids are unique WITHIN a level — so the `stair` below exists on both floors
# at the same coordinates, and that shared id is what declares one shaft: `describe --json`
# reports it under `vertical.connections`, and it is why the first floor needs no front
# door of its own to lint clean.
plan "Two-storey house" {
units mm
grid 50
paper A3 landscape
north up
dims auto all
let W = 8000 # outer width (wall centerlines)
let D = 7000 # outer depth
let T = 200 # exterior wall thickness
let HALL = 2200 # the stair hall / landing runs the full depth on the left
level 1 "Ground floor" {
wall id=shell exterior thickness T { (0,0) (W,0) (W,D) (0,D) close }
wall id=hall_w partition thickness 100 { (HALL,0) (HALL,D) }
wall id=kitchen_w partition thickness 100 { (HALL,3600) (W,3600) }
room id=hall at (0,0) size 2200x7000 label "Hall" uses hall
room id=living at (2200,0) size 5800x3600 label "Living" uses living
room id=kitchen at (2200,3600) size 5800x3400 label "Kitchen" uses kitchen
door id=front on shell at 21900 width 1000 swing into hall
door id=d_living on hall_w at 1800 width 900 swing into living
door id=d_kitchen on hall_w at 5300 width 900 swing into kitchen
window id=w_living on shell at 5000 width 1800
window id=w_kitchen on shell at 13300 width 1200
furniture sofa in living anchor top flush inset 300 size 2000x850 label "Sofa"
furniture kitchen_sink against wall kitchen_w offset 1200 in kitchen
stair id=stair at (100,1500) size 900x2600 dir up
}
level 2 "First floor" {
wall id=shell exterior thickness T { (0,0) (W,0) (W,D) (0,D) close }
wall id=hall_w partition thickness 100 { (HALL,0) (HALL,D) }
wall id=bath_w partition thickness 100 { (0,4600) (HALL,4600) }
wall id=bed_w partition thickness 100 { (HALL,3600) (W,3600) }
room id=landing at (0,0) size 2200x4600 label "Landing" uses circulation
room id=bath at (0,4600) size 2200x2400 label "Bath" uses bath
room id=bed1 at (2200,0) size 5800x3600 label "Bedroom 1" uses bedroom
room id=bed2 at (2200,3600) size 5800x3400 label "Bedroom 2" uses bedroom
# No exterior door up here: the `stair` below carries the same id as the ground
# floor's, so the access graph reads the landing as reached from outside via the
# storey below. (Before vertical circulation was modelled this floor needed a
# made-up balcony door to lint clean.)
door id=d_bed1 on hall_w at 1800 width 900 swing into bed1
door id=d_bed2 on hall_w at 4100 width 900 swing into bed2
door id=d_bath on bath_w at 1100 width 800 swing into bath
window id=w_bed1 on shell at 5000 width 1500
window id=w_bed2 on shell at 13300 width 1200
window id=w_bath on shell at 24200 width 600
furniture bed in bed1 anchor top-right flush inset 300 size 1500x2000 label "Double"
furniture bed in bed2 anchor bottom flush inset 300 size 1000x2000 label "Single"
furniture wc in bath anchor bottom-left flush size 400x700
furniture basin in bath anchor bottom-right flush size 600x450
stair id=stair at (100,1500) size 900x2600 dir down
# The landing looks down into the hall below over a balustrade: there is no floor
# here, so the plan says so. A `void` is drawn (dashed rectangle + both diagonals)
# and it OBSTRUCTS circulation — you cannot walk across a hole — with the walkable
# halo suppressed on every edge, because you can stand at the railing. It does NOT
# come off the landing's area: that number is the room's floor area and it is what
# the schedule and `describe --json` both report, so subtracting the well here would
# leave the drawing and the table disagreeing. `describe --json` lists it under
# `voids[]` with the room it falls in, which is where a net figure comes from.
void id=gallery at (100,200) size 900x1100
# The eaves, on the top storey only — this level's `shell` is the plan's one
# closed exterior ring on THIS page, so no `wall` clause is needed.
roof overhang 400
}
title {
project "Two-storey house"
drawn_by "ArchLang"
date "2026-07-26"
}
}
Public buildings
Museum (a real sheet, at a real scale)
A ~100 × 60 m single-level museum — 14 rooms, 6000 m², 7 doors, 9 windows — and the large-building flagship. Everything above is a dwelling, where sizing every annotation as a fraction of the drawing happens to look right; at 100 m it does not, and room labels come out metres tall. So this plan declares a sheet: paper A1 landscape + scale 1:200, which makes every annotation a fixed size on that sheet — 3.5 mm room labels and 0.5 mm wall lines whatever the building measures. It also drives dims auto all (the GB/T three-chain exterior dimensioning), a column grid written with for, and fixture rows placed by against wall <id> offset … so no coordinate is hand-computed.
# A ~100 x 60 m single-level museum — the LARGE-BUILDING flagship.
#
# What this example is for: everything else in examples/ is a dwelling, where the old
# "every size is a fraction of the drawing" rule happens to look right. At 100 m it does
# not — room labels come out metres tall. So this plan declares a SHEET:
#
# paper A1 landscape the drawing is issued on a real 841 x 594 mm sheet
# scale 1:200 which makes every annotation a fixed size ON THAT SHEET
# (3.5 mm room labels, 0.5 mm wall lines, 15 mm margins)
#
# A1 at 1:200 is the conventional GB/T presentation for a public building of this size:
# the 100 x 60 m footprint lands at 500 x 300 mm of ink with room for the three
# dimension chains and the title block. Drop the `scale` line and the sheet auto-fits
# the finest of 1:50 / 1:100 / 1:200 / 1:500 that fits — here, the same 1:200.
plan "City Museum" {
units mm
grid 100
paper A1 landscape
scale 1:200
north up
dims auto all
# ---- shell + structure ----------------------------------------------------
wall id=shell exterior thickness 300 { (0,0) (100000,0) (100000,60000) (0,60000) close }
# The spine: one 6 m corridor the full width of the building, with everything
# opening off it. Its two faces are the only walls the galleries need.
wall id=spine_n partition thickness 200 { (0,26000) (100000,26000) }
wall id=spine_s partition thickness 200 { (0,32000) (100000,32000) }
# North row dividers (galleries, then the service wing at the east end).
wall partition thickness 200 { (26000,0) (26000,26000) }
wall partition thickness 200 { (52000,0) (52000,26000) }
wall partition thickness 200 { (78000,0) (78000,26000) }
wall partition thickness 200 { (78000,14000) (100000,14000) }
# South row dividers (galleries, entrance hall, the WC stack and the cafe).
wall partition thickness 200 { (30000,32000) (30000,60000) }
wall partition thickness 200 { (48000,32000) (48000,60000) }
wall partition thickness 200 { (70000,32000) (70000,60000) }
wall id=wc_west partition thickness 200 { (76000,32000) (76000,60000) }
wall partition thickness 200 { (86000,32000) (86000,60000) }
wall id=wcw_s partition thickness 200 { (76000,42000) (86000,42000) }
wall id=wcm_s partition thickness 200 { (76000,52000) (86000,52000) }
# ---- north row ------------------------------------------------------------
room id=g1 at (0,0) size 26000x26000 label "Gallery 1"
room id=g2 at (26000,0) size 26000x26000 label "Gallery 2"
room id=g3 at (52000,0) size 26000x26000 label "Gallery 3"
room id=store at (78000,0) size 22000x14000 label "Store" uses storage
room id=office at (78000,14000) size 22000x12000 label "Offices" uses office
# ---- the spine ------------------------------------------------------------
room id=spine at (0,26000) size 100000x6000 label "Concourse" uses circulation hall
# ---- south row ------------------------------------------------------------
room id=g4 at (0,32000) size 30000x28000 label "Gallery 4"
room id=hall at (30000,32000) size 18000x28000 label "Entrance Hall" uses entry hall
room id=g5 at (48000,32000) size 22000x28000 label "Gallery 5"
room id=link at (70000,32000) size 6000x28000 label "Lobby" uses circulation
room id=wcw at (76000,32000) size 10000x10000 label "WC Women" uses wc
room id=wcm at (76000,42000) size 10000x10000 label "WC Men" uses wc
room id=plant at (76000,52000) size 10000x8000 label "Plant" uses utility
room id=cafe at (86000,32000) size 14000x28000 label "Cafe" uses dining
# ---- openings: the public route -------------------------------------------
# Main entrance (south facade) into the hall, then a wide threshold to the spine.
door at (39000,60000) width 2000 swing out
opening at (39000,32000) width 4000
# Galleries read off the spine through cased openings, not doors.
opening at (13000,26000) width 3000
opening at (39000,26000) width 3000
opening at (65000,26000) width 3000
opening at (15000,32000) width 3000
opening at (59000,32000) width 3000
opening at (73000,32000) width 3000
opening at (93000,32000) width 4000
# Service wing: offices off the spine, the store through the offices, and its own
# exterior service door on the north facade.
door at (89000,26000) width 1200
door at (89000,14000) width 1200
door at (89000,0) width 1200 swing out
# The WC stack + plant room open off the lobby.
door at (76000,37000) width 1200
door at (76000,47000) width 1200
door at (76000,56000) width 1200
# ---- glazing --------------------------------------------------------------
window at (13000,0) width 4000
window at (39000,0) width 4000
window at (65000,0) width 4000
window at (0,13000) width 3000
window at (0,46000) width 3000
window at (100000,20000) width 3000
window at (100000,45000) width 4000
window at (15000,60000) width 4000
window at (59000,60000) width 4000
# ---- structure: gallery column bays ---------------------------------------
# Four columns per north gallery, on the bay third-points (grid-snapped).
let BAY = 8700
for i in 0..3 {
let x0 = i * 26000
column at (x0 + BAY, 8700) size 600x600
column at (x0 + 2 * BAY, 8700) size 600x600
column at (x0 + BAY, 17400) size 600x600
column at (x0 + 2 * BAY, 17400) size 600x600
}
# The two deep south galleries carry their own bays.
for i in 0..2 {
column at (10000 + i * 10000, 41000) size 600x600
column at (10000 + i * 10000, 51000) size 600x600
column at (54000 + i * 12000, 41000) size 600x600
column at (54000 + i * 12000, 51000) size 600x600
}
# ---- sanitary fixtures ----------------------------------------------------
# `against wall <id>` places each piece BY CONSTRUCTION: the offset runs along that
# wall, the depth comes off its face, the rotation is derived from which side it lands
# on (`in <room>` picks the side), and a catalogued fixture needs no `size`. So a row
# of WCs is three offsets — no coordinates, no half-thickness arithmetic, and no
# fixture can end up floating or facing its own wall.
furniture wc against wall wcw_s offset 1500 in wcw
furniture wc against wall wcw_s offset 5000 in wcw
furniture wc against wall wcw_s offset 8500 in wcw
furniture basin against wall spine_s offset 79000 in wcw
furniture basin against wall spine_s offset 83000 in wcw
furniture wc against wall wcm_s offset 1500 in wcm
furniture wc against wall wcm_s offset 5000 in wcm
furniture wc against wall wcm_s offset 8500 in wcm
furniture basin against wall wcw_s offset 3000 in wcm
furniture basin against wall wcw_s offset 7000 in wcm
# ---- cafe fit-out ---------------------------------------------------------
furniture counter against wall spine_s offset 90000 size 6000x800 label "Servery" in cafe
furniture table at (89000,45000) size 1600x1600 label "Table" in cafe
furniture table at (93000,45000) size 1600x1600 label "Table" in cafe
# ---- reception ------------------------------------------------------------
furniture desk at (33000,38000) size 1800x800 label "Reception desk" in hall
furniture chair at (35200,39200) size 500x500 in hall
furniture chair at (33000,40500) size 500x500 in hall
furniture plant at (44000,55000) size 700x700 in hall
# ---- gallery seating & planting --------------------------------------------
# One bench and one plant per gallery — sparse-elegant, not empty — kept clear of
# the bay columns above.
furniture bench at (19000,20000) size 1800x500 in g1
furniture plant at (23000,2000) size 700x700 in g1
furniture bench at (45000,20000) size 1800x500 in g2
furniture plant at (49000,2000) size 700x700 in g2
furniture bench at (71000,20000) size 1800x500 in g3
furniture plant at (75000,2000) size 700x700 in g3
furniture bench at (24000,50000) size 1800x500 in g4
furniture plant at (26000,35000) size 700x700 in g4
furniture bench at (60000,55000) size 1800x500 in g5
furniture plant at (49000,35000) size 700x700 in g5
title { project "City Museum" drawn_by "ArchLang" date "2026-07-26" }
}
Harbour Aquarium (curves)
A 60 × 40 m public aquarium — 8 rooms, 2061.06 m², 3 doors, 6 windows, A2 at 1:200 — and the curved-geometry flagship. Everything above is rectilinear; an aquarium is the building type that is not. Its centrepiece is a cylindrical tank you walk around, written as a circular room (room circle at (cx,cy) radius R, an exact 201.06 m² = πR²) inside a drum wall made of two arc edges, and its public frontage turns the south-east corner as a quarter circle of R12000 instead of mitring it.
Two things to read off the drawing. The curved faces are true arcs — SVG A commands, native DXF ARC entities — so the rotunda never looks faceted however far you zoom in. And because a linear dimension chain cannot describe an arc, dims auto dimensions the round things the way GB/T 50104 does: an R leader per distinct arc and a φ call-out across the circular room, while the three exterior chains stay on the straight facades. Both rotunda doors sit on the curve — on rotunda at 25% walks the wall by run length, so an arc contributes its arc length rather than its chord — and their leaves swing off the tangent at the doorway.
# A ~60 x 46 m public aquarium — the CURVED-GEOMETRY flagship (v1.24).
#
# Everything else in examples/ is rectilinear, because until v1.24 the language was. An
# aquarium is the building type that is NOT: its centrepiece is a cylindrical tank you
# walk around, and its public frontage bows. This plan exercises both curve forms and
# nothing else that is new:
#
# arc (x,y) radius R [cw|ccw] [major] a CURVED WALL EDGE, from the previous vertex to
# this one along a circle of radius R. Default is
# the minor arc turning `ccw` as drawn.
# room circle at (cx,cy) radius R a CIRCULAR FLOOR. Its area is measured exactly
# (piR^2), never from the tessellation the grid
# and adjacency layers use.
#
# Two things to read off the drawing. (1) The curved faces are TRUE arcs — SVG `A`
# commands, DXF `ARC` entities — so the rotunda never looks faceted at any zoom.
# (2) `dims auto` cannot chain a curve, so it dimensions the round things the way GB/T
# 50104 does: an `R` leader per distinct arc and a `phi` call-out across the circular
# room, while the linear chains stay on the straight facades where they belong.
#
# Issued on A2 at 1:200, the conventional GB/T presentation for a public building this
# size, with the positioning axes on the straight structural grid only.
plan "Harbour Aquarium" {
units mm
grid 100
paper A2 landscape
scale 1:200
north up
dims auto all
schedule rooms
# ---- shell: three straight facades and one curved one ---------------------
#
# The south-east corner is turned as a quarter circle of R12000 rather than mitred —
# the curved public frontage facing the harbour. `cw` puts the centre on the RIGHT of
# travel (the wall is walked (60000,28000) -> (48000,40000), heading south-west), which
# is the inside of the building, so the facade bows OUTWARD. Swap it to `ccw` and the
# same two endpoints give the concave corner instead.
wall id=shell exterior thickness 300 {
(0,0)
(60000,0)
(60000,28000)
arc (48000,40000) radius 12000 cw
(0,40000)
close
}
# ---- the cylindrical tank: one full circle from two semicircular arcs -----
#
# There is no `close`-an-arc form on purpose: a closed curve is written as the two
# halves it is, which keeps every edge's radius visible in the source. Both are the
# default `ccw`, so the wall is walked east -> south -> west -> north -> east, and the
# `hinge`/`swing` of a door on it reads against THAT direction of travel (the tangent
# at the doorway), exactly as it would on a straight run.
#
# The circle is tangent to the concourse at its south point (30000,22000) and to the
# plant room at its north point (30000,6000), so a door at either lands on a real
# room boundary and connects two spaces.
wall id=rotunda partition thickness 200 {
(38000,14000)
arc (22000,14000) radius 8000
arc (38000,14000) radius 8000
}
# ---- straight structure ---------------------------------------------------
wall id=concourse_n partition thickness 200 { (0,22000) (21000,22000) }
wall id=concourse_n2 partition thickness 200 { (39000,22000) (60000,22000) }
wall id=concourse_s partition thickness 200 { (0,30000) (60000,30000) }
wall partition thickness 200 { (21000,0) (21000,22000) }
wall partition thickness 200 { (39000,0) (39000,22000) }
wall partition thickness 200 { (21000,6000) (39000,6000) }
wall id=foyer_w partition thickness 200 { (20000,30000) (20000,40000) }
wall id=foyer_e partition thickness 200 { (40000,30000) (40000,40000) }
# ---- rooms ----------------------------------------------------------------
# `dims auto` puts the phi call-out ON the diameter, through the centre — which is where
# GB/T wants it and where the room name would otherwise be. Moving the NAME is the
# author's job (the compiler never relocates a dimension to make room for a label), so
# this one is pinned above the centre line with `label … at`.
room id=rotunda_r circle at (30000,14000) radius 8000 label "Ocean Rotunda" at (30000,10500) uses hall
room id=reef at (0,6000) size 21000x16000 label "Reef Gallery" uses living
room id=kelp at (39000,6000) size 21000x16000 label "Kelp Forest" uses living
room id=plant at (21000,0) size 18000x6000 label "Filtration" uses utility
room id=concourse at (0,22000) size 60000x8000 label "Concourse" uses circulation
room id=foyer at (20000,30000) size 20000x10000 label "Entrance Foyer" uses entry
room id=shop at (0,30000) size 20000x10000 label "Shop" uses office
room id=cafe at (40000,30000) size 20000x10000 label "Cafe" uses dining
# ---- openings -------------------------------------------------------------
#
# Both rotunda doors sit ON the curve. `on rotunda at <pct>` walks the wall by RUN
# length — an arc contributes its arc length R*theta, not its chord — so 25% is a
# quarter of the way round from the wall's start at the EAST point. The default `ccw`
# runs east -> north -> west -> south, so 25% is the north point (the staff door into
# Filtration) and 75% the south one (the public door off the Concourse).
door id=d_rot_n on rotunda at 25% width 1200
door id=d_rot_s on rotunda at 75% width 1800
# The main entrance is on the CURVED facade, at 15% of the shell's run measured from
# its start — which lands on the straight south wall; the pair of harbour-side windows
# below sit on the arc itself, their panes along the tangent and their jambs radial.
door id=d_main at (30000,40000) width 2400 wall shell swing in
window id=w_arc1 on shell at 47% width 2000
window id=w_arc2 on shell at 50% width 2000
window at (10000,0) width 3000 wall shell
window at (50000,0) width 3000 wall shell
window at (0,14000) width 3000 wall shell
window at (60000,14000) width 3000 wall shell
opening id=o_shop at (20000,35000) width 2400 wall foyer_w
opening id=o_cafe at (40000,35000) width 2400 wall foyer_e
opening id=o_foyer at (30000,30000) width 4000 wall concourse_s
opening id=o_reef at (10500,22000) width 3000 wall concourse_n
opening id=o_kelp at (49500,22000) width 3000 wall concourse_n2
# ---- fit-out ----------------------------------------------------------------
# Benches ring the rotunda at the four quadrants clear of the cardinal doors
# (25%/75% north/south on the drum). A ticket desk sits in the foyer, and the
# cafe gets a few tables — kept west of the curved south-east facade, where the
# shell's mitred `arc` corner does not intrude on the room's rectangle.
furniture bench at (34450,9550) size 1200x450 in rotunda_r
furniture bench at (25550,9550) size 1200x450 in rotunda_r
furniture bench at (25550,18450) size 1200x450 in rotunda_r
furniture bench at (34450,18450) size 1200x450 in rotunda_r
furniture desk at (22000,33000) size 1800x800 label "Ticket desk" in foyer
furniture chair at (24200,33200) size 500x500 in foyer
furniture plant at (37000,32000) size 700x700 in foyer
furniture table at (43000,32000) size 1400x1400 label "Table" in cafe
furniture table at (47000,32000) size 1400x1400 label "Table" in cafe
furniture table at (43000,35500) size 1400x1400 label "Table" in cafe
furniture plant at (41000,38500) size 600x600 in cafe
# ---- positioning axes: the STRAIGHT structural grid only ------------------
# A curve has no datum line, so it carries none; its radius is its dimension.
axes {
x at 0
x at 21000
x at 39000
x at 60000
y at 0
y at 22000
y at 30000
y at 40000
}
}
Reading Room Library (sheet tables)
A 50 × 32 m public library around a circular reading room — 14 rooms, 1545.06 m², 8 doors, 16 windows, paper A2 landscape at scale 1:200, split into three zones: public (9 rooms, 948.06 m²), stacks (2 rooms, 374.5 m²) and staff (3 rooms, 222.5 m²).
The reading room is a drum inscribed in the 16 × 16 m central bay — a room circle of exactly 201.06 m² inside a wall of arc edges — so it is tangent to all four bay walls and every tangent point is a threshold; the four corner spandrels it leaves belong to no room and carry no schedule row. The circle is written as eight arcs rather than the two semicircles the shape needs, deliberately: the analysis grid behind describe --json's circulation rasterises a wall between its vertices, so two semicircles lay a phantom chord straight across the room and report the walk to it at five times its true length. Eight keeps every chord inside the face it stands for, and the drawing is identical either way — every face is a true arc, at any zoom.
The sheet is the other half. schedule rooms and legend fill the margin and are fully derived, nothing to configure: the legend lists one row per wall hatch material actually used and one per placed fixture category that has a plan symbol, and the schedule's rows and total are the same numbers arch describe --json reports. At the foot of the file, dim radius <wallId> segment <n> and dim diameter <roomId> take both geometry and text from the element they name, so R8000 and the centre are never retyped.
# A ~50 x 32 m public library around a circular reading room — the SHEET-AND-TABLES example.
#
# 1. CURVED GEOMETRY. The reading room is a drum: a `room circle` for the floor and a
# `wall` of `arc` edges for the enclosure, the pair `examples/aquarium.arch` introduces.
# The drum is INSCRIBED in the 16 x 16 m central bay, so it is tangent to all four bay
# walls and every tangent point is a threshold; the four corner spandrels it leaves
# (~14 m2 each) belong to no room and carry no schedule row. The circle is written as
# EIGHT arcs rather than the two semicircles the shape needs, deliberately: the analysis
# grid behind `describe --json`'s `circulation` rasterises a wall between its VERTICES,
# so two semicircles lay a phantom chord straight across the room and report the walk to
# it at 5x its true length. Eight keeps every chord inside the face it stands for, and
# the DRAWING is identical either way — every face is a true arc, at any zoom.
#
# 2. THE SHEET. `paper A2 landscape` + `scale 1:200` fixes every annotation at a size ON
# THE SHEET instead of a fraction of a 50 m building, and `schedule rooms` + `legend`
# fill the margin. Both tables are fully derived, nothing to configure: the legend lists
# one row per wall hatch MATERIAL used (`concrete` for the shell, the default poche for
# the partitions) and one per placed fixture CATEGORY that has a plan symbol — wc, basin,
# kitchen_sink, counter, fridge, stove. Use neither and the table comes out empty.
#
# 3. THE EXPLICIT CURVE CALL-OUTS at the foot of the file. `dim radius <wallId> segment <n>`
# and `dim diameter <roomId>` take BOTH geometry and text from the element they name, so
# R8000 and the centre are never retyped. `dims auto` already synthesizes one R per
# DISTINCT arc (deduplicated by centre+radius, so this eight-arc circle still gets one)
# and one phi per circular room, in every mode but `walls` — so these two statements are
# what the sheet would still carry with `dims auto` switched off, and they cost nothing
# while it is on. Both are zero-offset by contract, which is exactly why neither can trip
# `W_DIM_INSIDE` or `W_DIM_OVERLAP`: a zero-offset dim sits ON what it measures.
#
# Departments are `zone`s — declared, never inferred from position, and carrying zero
# geometric semantics: the SVG is byte-identical without them. All they add is `zones[]` on
# `describe --json` and the subtotal grouping in the room schedule.
plan "Reading Room Library" {
units mm
# `grid 50`, not 100: the partitions are 200 thick and the shell 300, so a fixture
# placed `flush` against the shell lands on a ...50 coordinate (32000 - 150). A grid of
# 100 could not express that face and would snap the fixture back into the wall.
grid 50
paper A2 landscape
scale 1:200
north up
dims auto all
schedule rooms
legend
# ---- structure: shell in `material concrete`, partitions in the default poche ------
wall id=shell exterior thickness 300 material concrete { (0,0) (50000,0) (50000,32000) (0,32000) close }
# The two spine walls: west wing | central bay | east wing.
wall id=w_west partition thickness 200 { (17000,0) (17000,32000) }
wall id=w_east partition thickness 200 { (33000,0) (33000,32000) }
# The bay's cross walls, and the drum inscribed between all four.
wall id=w_ref_s partition thickness 200 { (17000,8000) (33000,8000) }
wall id=w_hall_n partition thickness 200 { (17000,24000) (33000,24000) }
# Every vertex below is an exact grid-50 point at R8000 from (25000,16000) — 4800/6400 is
# a 3-4-5 triple scaled — so the eight arcs close a true circle. Walked from the EAST
# point the default `ccw` runs east -> north -> west -> south, which puts the thresholds
# at 25% (north), 50% (west) and 75% (south) of the wall's RUN; an arc contributes its
# arc length R*theta to that run, never its chord.
wall id=w_drum partition thickness 200 {
(33000,16000)
arc (29800,9600) radius 8000
arc (25000,8000) radius 8000
arc (20200,9600) radius 8000
arc (17000,16000) radius 8000
arc (20200,22400) radius 8000
arc (25000,24000) radius 8000
arc (29800,22400) radius 8000
arc (33000,16000) radius 8000
}
# Both wings carry a 3 m aisle on the drum's centre line, so each meets it at a tangent.
wall id=w_waisle_n partition thickness 200 { (0,14500) (17000,14500) }
wall id=w_waisle_s partition thickness 200 { (0,17500) (17000,17500) }
wall id=w_cafe_e partition thickness 200 { (44000,0) (44000,14500) }
wall id=w_eaisle_n partition thickness 200 { (33000,14500) (50000,14500) }
wall id=w_eaisle_s partition thickness 200 { (33000,17500) (50000,17500) }
wall id=w_staff_s partition thickness 200 { (33000,24000) (50000,24000) }
wall id=w_lobby_s partition thickness 200 { (33000,27000) (50000,27000) }
wall id=w_wcw_e partition thickness 200 { (39000,27000) (39000,32000) }
wall id=w_wcm_e partition thickness 200 { (45000,27000) (45000,32000) }
# ---- axes: the STRAIGHT structural grid only; a curve's radius is its dimension ----
axes {
x at 0, 17000, 33000, 44000, 50000
y at 0, 14500, 17500, 24000, 27000, 32000
}
# ---- rooms, grouped by department -----------------------------------------
zone public "Public" {
room id=r_hall at (17000,24000) size 16000x8000 label "Entrance Hall" uses entry hall
room id=r_reading circle at (25000,16000) radius 8000 label "Reading Room" at (25000,17200) uses hall
room id=r_children at (0,17500) size 17000x14500 label "Children's Library" uses living
room id=r_waisle at (0,14500) size 17000x3000 label "West Aisle" uses circulation
room id=r_cafe at (33000,0) size 11000x14500 label "Cafe" uses dining
room id=r_eaisle at (33000,14500) size 17000x3000 label "East Aisle" uses circulation
room id=r_lobby at (33000,24000) size 17000x3000 label "WC Lobby" uses circulation
room id=r_wc_w at (33000,27000) size 6000x5000 label "WC Women" uses wc
room id=r_wc_m at (39000,27000) size 6000x5000 label "WC Men" uses wc
}
zone stacks "Stacks" {
room id=r_stacks at (0,0) size 17000x14500 label "Open Stacks" at (8500,12400) uses storage
room id=r_ref at (17000,0) size 16000x8000 label "Reference Stacks" uses storage
}
zone staff "Staff" {
room id=r_staff at (33000,17500) size 17000x6500 label "Staff Workroom" uses office
room id=r_kitchen at (44000,0) size 6000x14500 label "Cafe Kitchen" uses kitchen
room id=r_plant at (45000,27000) size 5000x5000 label "Plant Room" uses utility
}
# ---- thresholds -----------------------------------------------------------
# The drum is tangent to each bay wall at one point, so a threshold there has to be cut
# through BOTH faces — the arc and the straight wall it touches — or the poche of the
# second one still stands in the doorway. Each pair below is one physical opening.
opening id=o_drum_s on w_drum at 75% width 3000
opening id=o_hall_n on w_hall_n at 50% width 3000
opening id=o_drum_n on w_drum at 25% width 3000
opening id=o_ref_s on w_ref_s at 50% width 3000
opening id=o_drum_w on w_drum at 50% width 2400
opening id=o_west_16 on w_west at 16000 width 2400
# The rest of the public route.
door id=d_main at (25000,32000) width 2400 wall shell swing into r_hall
opening id=o_hall_children on w_west at 28000 width 3000
opening id=o_hall_lobby on w_east at 25500 width 3000
opening id=o_ref_stacks on w_west at 4000 width 2400
opening id=o_stacks_aisle on w_waisle_n at 50% width 3000
opening id=o_children_aisle on w_waisle_s at 50% width 3000
opening id=o_cafe_aisle on w_eaisle_n at 5500 width 3000
opening id=o_staff_aisle on w_eaisle_s at 8500 width 2400
# Staff and service doors. The cafe kitchen keeps a pocket door off the aisle so no leaf
# swings into a 3 m corridor, and its own service door onto the east yard.
door id=d_kitchen pocket on w_eaisle_n at 14000 width 1000 slide right
door id=d_cafe_kitchen on w_cafe_e at 50% width 1000 swing into r_kitchen
door id=d_service at (50000,13000) width 1200 wall shell swing out
door id=d_staff_lobby on w_staff_s at 2500 width 1000 swing into r_lobby
door id=d_wc_w on w_lobby_s at 1500 width 1000 swing into r_wc_w
door id=d_wc_m on w_lobby_s at 7500 width 1000 swing into r_wc_m
door id=d_plant on w_lobby_s at 13000 width 900 swing into r_plant
# ---- glazing --------------------------------------------------------------
window at (6000,0) width 4000 wall shell
window at (12000,0) width 4000 wall shell
window at (21000,0) width 3000 wall shell
window at (29000,0) width 3000 wall shell
window at (38500,0) width 4000 wall shell
window at (0,6000) width 3000 wall shell
window at (0,16000) width 2000 wall shell
window at (0,24000) width 4000 wall shell
window at (50000,10500) width 1600 wall shell
window at (50000,20500) width 3000 wall shell
window at (6000,32000) width 4000 wall shell
window at (12000,32000) width 4000 wall shell
window at (20000,32000) width 3000 wall shell
window at (30000,32000) width 3000 wall shell
window at (36000,32000) width 1200 wall shell
window at (42000,32000) width 1200 wall shell
# ---- vertical circulation, side by side in the hall -----------------------
# A single-storey plan still draws both symbols; `W_STAIR_UNMATCHED` is a multi-storey
# question and never fires here.
stair id=st_main at (28500,26500) size 1400x4500 dir up
elevator id=lift_main at (30500,28000) size 2200x2200
# ---- fit-out --------------------------------------------------------------
# Reading tables ride the drum's chord lines, leaving the centre band clear for the phi
# call-out and the west threshold.
furniture table at (21500,9800) size 7000x1400 label "Reading table"
furniture table at (20500,12200) size 9000x1400 label "Reading table"
furniture table at (20500,18400) size 9000x1400 label "Reading table"
furniture table at (21500,20800) size 7000x1400 label "Reading table"
for i in 0..5 {
furniture shelf at (2000, 1500 + i * 2200) size 13000x600 label "Stacks"
}
for i in 0..4 {
furniture shelf at (18500 + i * 3200, 1200) size 600x5000 label "Reference"
}
for i in 0..3 {
furniture table at (2500 + i * 4500, 19500) size 2000x2000 label "Table"
furniture table at (2500 + i * 4500, 23500) size 2000x2000 label "Table"
furniture shelf at (2000 + i * 4500, 28500) size 3000x500 label "Picture books"
}
for i in 0..5 {
furniture desk at (34500 + i * 3000, 18500) size 1600x800 label "Desk"
furniture desk at (34500 + i * 3000, 22000) size 1600x800 label "Desk"
}
for i in 0..3 {
furniture table at (34500 + i * 3000, 2500) size 1600x1600 label "Table"
furniture table at (34500 + i * 3000, 6500) size 1600x1600 label "Table"
}
furniture sofa at (2000,27500) size 2400x900 label "Story bench"
furniture sofa at (18000,30000) size 2400x900 label "Seating"
furniture sofa at (21000,30000) size 2400x900 label "Seating"
furniture counter against wall w_hall_n offset 4000 size 4000x700 label "Enquiries" in r_hall
furniture plant at (18500,29000) size 700x700 in r_hall
furniture counter against wall w_cafe_e offset 3000 size 4000x700 label "Servery" in r_cafe
# Catalogued fixtures need no `size`: the footprint, the depth off the wall face and the
# quarter-turn all come from the wall and the catalogue.
furniture table at (45000,2500) size 3600x900 label "Prep"
furniture table at (45000,4600) size 3600x900 label "Prep"
furniture kitchen_sink against wall shell segment 1 offset 2000 in r_kitchen
furniture counter against wall shell segment 1 offset 3600 in r_kitchen
furniture fridge against wall shell segment 1 offset 5200 in r_kitchen
furniture stove against wall shell segment 1 offset 6800 in r_kitchen
furniture wc against wall shell segment 2 offset 11500 in r_wc_w
furniture wc against wall shell segment 2 offset 13000 in r_wc_w
furniture wc against wall shell segment 2 offset 14500 in r_wc_w
furniture basin against wall w_east offset 28000 in r_wc_w
furniture basin against wall w_east offset 29500 in r_wc_w
furniture wc against wall shell segment 2 offset 5500 in r_wc_m
furniture wc against wall shell segment 2 offset 7000 in r_wc_m
furniture wc against wall shell segment 2 offset 8500 in r_wc_m
furniture basin against wall w_wcw_e offset 1000 in r_wc_m
furniture basin against wall w_wcw_e offset 2500 in r_wc_m
# ---- the explicit curve call-outs (see the head of the file) --------------
dim radius w_drum segment 0
dim diameter r_reading
title {
project "Reading Room Library"
drawn_by "ArchLang"
date "2026-08-16"
}
}
Transit Hall
A 90 × 40 m metro concourse — 12 rooms, 3600 m², 7 doors, 19 windows, A2 at 1:200 — and the big-public-building example. One fact organises the whole plan: a transit hall is two buildings that share a roof. The unpaid side is street, and anyone may stand in it; the paid side is platform, and the only way across is the gate line. So the drawing is a single 300 mm partition running the full 90 m width, with nine openings punched in it by a for loop — eight 800 mm gates and one 1200 mm wide aisle. Nothing else joins the two halves.
That split is declared, never inferred from position: paid is 1260 m² over 1 room, unpaid 1948 m² over 5, and back-of-house 80 m² over 2, so "how much unpaid floor is there" is a read rather than a sum — while the SVG is byte-identical without the zone wrappers. The four retail units are one strip, not four hand-placed rooms: each unit's offset is the running sum of the ones before it, so changing a shop's depth moves every unit below it and nothing else. Escalators and a lift are vertical shafts drawn with their chevrons and UP/DN arrows; on a single-storey plan they still obstruct circulation, and W_STAIR_UNMATCHED is a multi-storey question that never fires here.
# A 90 x 40 m metro concourse split by its gate line — the BIG-PUBLIC-BUILDING example.
#
# One fact organises the whole plan: a transit hall is two buildings that share a roof. The
# UNPAID side is street, and anyone may stand in it — shops, WCs, ticket machines. The PAID
# side is platform, and the only way across is the gate line. So the drawing is a single
# 300 mm partition, `w_gate`, running the full 90 m width, with nine `opening`s punched in
# it by a `for` loop: eight 800 mm gates and one 1200 mm wide aisle. Nothing else joins the
# two halves — the retail strip and the service wing both stop dead at that wall.
#
# Three things this example is the showcase for:
#
# `zone` the paid / unpaid / service split is DECLARED, never inferred from
# position. It draws nothing (the SVG is byte-identical without it), but the
# room schedule groups by it with a subtotal per zone, and `describe --json`
# reports `zones[]` — so "how much unpaid floor is there" is a read, not a sum.
# `strip` the four retail units are one block, not four hand-placed rooms. Each unit's
# offset is the running sum of the ones before it, so changing a shop's depth
# moves every unit below it and nothing else.
# escalators `escalator … dir up|down` and `elevator` are one shaft each, drawn with the
# chevrons and the UP/DN arrow. On a single-storey plan they still draw and
# still obstruct circulation; `W_STAIR_UNMATCHED` is a multi-storey question
# and never fires here. Both narrow ends of an escalator are entries.
#
# The columns are on the 12 m structural grid the `axes` bubbles name, laid by `for` — one
# statement per row, not eighteen coordinates.
plan "Transit Hall" {
units mm
# `grid 50`: partitions are 200 thick and the shell 400, so a fixture placed against a
# face lands on a ...50 coordinate a 100 grid could not express.
grid 50
paper A2 landscape
scale 1:200
north up
dims auto all
schedule rooms
let BAY = 12000 # structural bay, and the axis grid
let GATE = 14000 # y of the gate line
# ---- shell and the gate line ----------------------------------------------
wall id=shell exterior thickness 400 material concrete { (0,0) (90000,0) (90000,40000) (0,40000) close }
wall id=w_gate partition thickness 300 { (0,GATE) (90000,GATE) }
# ---- unpaid side: retail strip (west) and the service wing (east) ---------
wall id=w_retail_e partition thickness 200 { (12000,GATE) (12000,40000) }
wall id=w_r1 partition thickness 200 { (0,20500) (12000,20500) }
wall id=w_r2 partition thickness 200 { (0,27000) (12000,27000) }
wall id=w_r3 partition thickness 200 { (0,33500) (12000,33500) }
wall id=w_wing_w partition thickness 200 { (76000,GATE) (76000,40000) }
wall id=w_wing_e partition thickness 200 { (80000,GATE) (80000,40000) }
wall id=w_wcw_s partition thickness 200 { (80000,21000) (90000,21000) }
wall id=w_wcm_s partition thickness 200 { (80000,28000) (90000,28000) }
wall id=w_wca_s partition thickness 200 { (80000,32000) (90000,32000) }
wall id=w_staff_s partition thickness 200 { (80000,36000) (90000,36000) }
axes {
x at 0, 12000, 24000, 36000, 48000, 60000, 72000, 90000
y at 0, 14000, 27000, 40000
}
# ---- rooms ----------------------------------------------------------------
# Four retail units laid end to end down the west edge: only each unit's DEPTH is
# written, so changing one shop moves every unit below it and nothing else. `strip` is a
# PLAN-LEVEL block — it cannot nest inside a `zone` (E_STRIP_NEST) — so the kiosks are the
# one group the schedule lists on its own, after the three zoned departments.
strip down at (0,GATE) gap 0 width 12000 {
room id=r_shop1 size 6500 label "Kiosk 1" uses office
room id=r_shop2 size 6500 label "Kiosk 2" uses office
room id=r_shop3 size 6500 label "Kiosk 3" uses office
room id=r_shop4 size 6500 label "Kiosk 4" uses office
}
zone paid "Paid" {
room id=r_paid at (0,0) size 90000x14000 label "Paid Concourse" uses hall circulation
}
zone unpaid "Unpaid" {
room id=r_unpaid at (12000,GATE) size 64000x26000 label "Unpaid Concourse" uses hall circulation
room id=r_lobby at (76000,GATE) size 4000x26000 label "Lobby" uses circulation
room id=r_wc_w at (80000,GATE) size 10000x7000 label "WC Women" uses wc
room id=r_wc_m at (80000,21000) size 10000x7000 label "WC Men" uses wc
room id=r_wc_a at (80000,28000) size 10000x4000 label "Accessible WC" uses wc
}
zone service "Back of house" {
room id=r_staff at (80000,32000) size 10000x4000 label "Staff Room" uses office
room id=r_control at (80000,36000) size 10000x4000 label "Control Room" uses office
}
# ---- the gate line --------------------------------------------------------
# `on <wall> at <pos>` takes a LITERAL position, so a generated run of gates uses the
# absolute form instead: the point is on the gate wall's centreline by construction
# (y = GATE), and `wall w_gate` names the host. Eight standard gates, then the wide aisle.
for i in 0..8 {
opening at (30000 + i * 2400, GATE) width 800 wall w_gate
}
opening id=o_gate_wide on w_gate at 49200 width 1200
# ---- the rest of the route ------------------------------------------------
door id=d_main at (45000,40000) width 4000 wall shell swing into r_unpaid
opening id=o_shop1 on w_retail_e at 3250 width 2400
opening id=o_shop2 on w_retail_e at 9750 width 2400
opening id=o_shop3 on w_retail_e at 16250 width 2400
opening id=o_shop4 on w_retail_e at 22750 width 2400
opening id=o_lobby_n on w_wing_w at 4000 width 3000
opening id=o_lobby_s on w_wing_w at 22000 width 2400
door id=d_wc_w on w_wing_e at 3500 width 1000 swing into r_wc_w
door id=d_wc_m on w_wing_e at 10500 width 1000 swing into r_wc_m
door id=d_wc_a on w_wing_e at 16000 width 1100 swing into r_wc_a
# Back of house takes pocket doors: a 4 m lobby is a queue, and neither leaf may sweep
# into it. `slide` runs along the wall's traversal (north to south here), so each panel
# drives into the solid run between the two doors rather than at a corner.
door id=d_staff pocket on w_wing_e at 20000 width 900 slide right
door id=d_control pocket on w_wing_e at 24000 width 900 slide left
door id=d_staff_entry at (90000,38500) width 1200 wall shell swing out
# ---- glazing --------------------------------------------------------------
# Curtain-wall bays on the three street facades, written as three runs rather than
# seventeen statements. Every centre is on the shell's centreline by construction.
for i in 0..4 {
window at (15000 + i * 20000, 0) width 6000 wall shell # paid-side north facade
window at (0, 17000 + i * 6500) width 3000 wall shell # one per kiosk, west facade
}
for i in 0..3 {
window at (18000 + i * 9000, 40000) width 6000 wall shell # south facade, west of the entrance
window at (54000 + i * 9000, 40000) width 6000 wall shell # south facade, east of the entrance
}
window at (0,7000) width 3000 wall shell
window at (90000,7000) width 3000 wall shell
window at (90000,17500) width 1200 wall shell
window at (90000,24500) width 1200 wall shell
window at (90000,34000) width 1500 wall shell
# ---- vertical circulation down to the platforms ---------------------------
escalator id=esc_down at (41000,3000) size 1400x9000 dir down
escalator id=esc_up at (44000,3000) size 1400x9000 dir up
elevator id=lift_plat at (50500,3000) size 2400x2400
# ---- structure: one statement per column row ------------------------------
for i in 0..6 {
column at (BAY + i * BAY, 7000) size 800x800
}
for i in 0..5 {
column at (2 * BAY + i * BAY, 27000) size 800x800
}
# ---- sanitary fixtures ----------------------------------------------------
# `against wall <id> offset <mm>` puts the piece's CENTRE at that distance along the
# wall; the depth off the face, the quarter-turn and (for a catalogued fixture) the
# footprint are all derived, so no coordinate and no half-thickness is written here.
for i in 0..4 {
furniture wc against wall w_wcw_s offset 1500 + i * 1500 in r_wc_w
furniture wc against wall w_wcm_s offset 1500 + i * 1500 in r_wc_m
}
furniture basin against wall w_gate offset 87000 in r_wc_w
furniture basin against wall w_gate offset 88500 in r_wc_w
furniture basin against wall w_wcw_s offset 7000 in r_wc_m
furniture basin against wall w_wcw_s offset 8500 in r_wc_m
furniture wc against wall w_wca_s offset 1500 in r_wc_a
furniture basin against wall w_wca_s offset 3500 in r_wc_a
# ---- concourse fit-out ----------------------------------------------------
# Ticket machines back onto the gate wall on the unpaid side — `offset` is the distance
# along the wall to the piece's CENTRE, and `size` on an `against` piece reads
# along x depth, so the 6 m bank spans 17-23 m and stands 900 off the face.
furniture counter against wall w_gate offset 20000 size 6000x900 label "Ticket machines" in r_unpaid
furniture counter against wall w_gate offset 60000 size 6000x900 label "Ticket machines" in r_unpaid
# The assistance desk sits just east of the wide gate, clear of every gate approach.
furniture desk at (52000,16500) size 4000x1200 label "Assistance"
# Benches stop short of the escalator/lift block (x >= 41000 on the paid side).
for i in 0..4 {
furniture bench at (30000 + i * 6000, 34000) size 2400x700 label "Bench"
furniture bench at (18000 + i * 6000, 5000) size 2400x700 label "Bench"
}
furniture desk at (82000,34000) size 3000x900 label "Desk" in r_staff
furniture desk at (82000,38000) size 3000x900 label "Desk" in r_control
# ---- light touch: the four kiosks and the WC lobby were otherwise bare -----
furniture shelf at (1000,16400) size 900x300 label "Shelving" in r_shop1
furniture plant at (10200,16700) size 600x600 in r_shop1
furniture shelf at (1000,22900) size 900x300 label "Shelving" in r_shop2
furniture plant at (10200,23200) size 600x600 in r_shop2
furniture shelf at (1000,29400) size 900x300 label "Shelving" in r_shop3
furniture plant at (10200,29700) size 600x600 in r_shop3
furniture shelf at (1000,35900) size 900x300 label "Shelving" in r_shop4
furniture plant at (10200,36200) size 600x600 in r_shop4
furniture bench at (77800,24500) size 450x1200 in r_lobby
furniture plant at (78800,20500) size 500x500 in r_lobby
title {
project "Metro Interchange — concourse level"
drawn_by "ArchLang"
date "2026-08-16"
}
}
Clinic Wing
An outpatient clinic — 13 rooms, 294.12 m², 11 doors, 13 windows at scale 1:100 — whose six consulting rooms are one component placed six times. Every consulting room in a clinic is the same room; drawn the usual way that is twenty-four statements to keep in step by hand. Here it is written once and instantiated: each place consult(…) as c1 … is an addressable instance of exactly 16.2 m².
Three things follow. Namespacing — ids inside the component become <instance>.<id>, so the six rooms are c1.main … c6.main and the plan can address exactly one of them: furniture … in c2.main puts an ECG trolley in the second room and nowhere else, and arch describe --room c2.main reads it back. Mirroring — the north row is placed as drawn and the south row with mirror y, a real reflection about the instance's own origin, so the corridor wall lands on the corridor for both rows and all six doors face the same way; because a reflection moves the origin to the far edge, a mirrored 4500-deep unit is placed at the far y. One side wall — each instance carries its left wall and its corridor wall only, an L rather than a box, so at 3600 centres no two partitions ever coincide. The rest of the wing is ordinary: a 2400 corridor with a dim clear proving 2300 mm of it is actually clear floor, pocket doors on the consulting rooms so no leaf ever sweeps into a corridor patients queue in, and a sliding door wide enough for a trolley into treatment. The plan is clean under arch lint --profile accessibility-advisory as well as the default ruleset.
# An outpatient clinic wing whose six consulting rooms are ONE component placed six times.
#
# Every consulting room in a clinic is the same room. Drawn the usual way that is six
# copies of four walls, a door, a desk and a basin — twenty-four statements that must be
# kept in step by hand, and a plan where widening the rooms means editing every one. So
# this plan writes the room ONCE, as a component, and instantiates it:
#
# component consult(depth, no) { … } # authored in LOCAL coords from (0,0)
# place consult(4500, 1) as c1 at (9000,0) # an addressable INSTANCE
#
# Three things follow from that, and they are what this example is the showcase for:
#
# NAMESPACING ids inside the component become `<instance>.<id>`, so the six rooms are
# `c1.main` … `c6.main` and the plan can address exactly one of them —
# `furniture … in c2.main` below puts an ECG trolley in the second room
# and nowhere else. `arch describe --room c2.main` reads it back.
# MIRRORING the north row is placed as drawn; the south row is placed with
# `mirror y`, a REAL reflection about the instance's own origin. So the
# corridor wall — local `y = depth` — lands on the corridor for both rows
# and all six doors face the same way. Because a reflection moves the
# origin to the far edge, a mirrored 4500-deep unit is placed at the FAR
# y (11400), not at 6900.
# ONE SIDE WALL each instance carries its LEFT wall and its CORRIDOR wall only — an L,
# not a box. Laid at 3600 centres, each unit's left wall closes the one
# before it, so no two partitions ever coincide; the plan supplies the
# single wall that closes the last unit of each row.
#
# The rest of the wing is written the ordinary way: a 2400 corridor down the middle with
# a `dim clear` proving 2300 of it is actually clear floor, the public rooms (entrance,
# reception, waiting) at the west end and the clinical support rooms (treatment, staff,
# accessible WC) at the east, declared as two `zone`s. Door kinds carry the same argument
# a builder would: the consulting rooms take `pocket` doors so no leaf ever sweeps into a
# corridor patients queue in, and the treatment room a `sliding` one wide enough for a
# trolley. This plan is clean under `arch lint --profile accessibility-advisory` as well
# as the default ruleset.
plan "Clinic Wing" {
units mm
# `grid 50`: every partition here is 100 thick, so a fixture backed onto one lands on a
# half-thickness (…50) coordinate that a 100 grid could not express without snapping it
# back into the wall.
grid 50
paper A3 landscape
scale 1:100
north up
dims auto all
# A `schedule` AND a `legend`. The schedule groups rooms by the innermost zone the
# author DECLARED — `public` and `clinical` — and a `place`d instance's implicit zone
# (`clinical.c1` …) is transparent to that grouping, so the six consult rooms sit under
# "Clinical" instead of printing six one-row groups with six subtotals. (They did print
# six, before v1.26.2; this file shipped with a legend and no schedule because of it.)
# `describe --zone clinical.c4` still addresses one instance — a `place` is a namespace,
# not a table heading.
schedule rooms
legend
# ---- the consulting room, written once -------------------------------------
# `depth` is the room's depth; `no` only feeds the label. The corridor is at local
# `y = depth`, which is the edge `mirror y` turns over.
component consult(depth, no) {
wall id=w_side partition thickness 100 { (0,0) (0,depth) }
wall id=w_corr partition thickness 100 { (0,depth) (3600,depth) }
room id=main at (0,0) size 3600 x depth label "Consult {no}" uses office
# A pocket door: the panel slides into the 1300 mm of wall past its jamb (the run
# `W_POCKET_RUN` measures), so nothing swings into the corridor. 1000 wide, not the
# residential 900: at 900 every one of these routes reads 840 mm of clear width and
# `arch lint --profile accessibility-advisory` says so, room by room.
door id=d pocket on w_corr at 50% width 1000 slide right
furniture id=f_desk desk in main anchor top inset 900 size 1600x800 label "Desk"
furniture id=f_couch couch in main anchor right inset 200 size 700x1900 label "Couch"
furniture id=f_basin basin against wall w_side offset depth - 800 in main
}
# ---- shell and the walls the plan itself owns -------------------------------
wall id=shell exterior thickness 200 { (0,0) (25800,0) (25800,11400) (0,11400) close }
wall id=w_wb partition thickness 100 { (0,6900) (9000,6900) }
wall id=w_wb_d partition thickness 100 { (4500,6900) (4500,11400) }
wall id=w_corr_w partition thickness 100 { (9000,4500) (9000,6900) }
wall id=w_mid_n partition thickness 100 { (19800,0) (19800,4500) }
wall id=w_mid_s partition thickness 100 { (19800,6900) (19800,11400) }
wall id=w_tr_s partition thickness 100 { (19800,4500) (25800,4500) }
wall id=w_ea_n partition thickness 100 { (19800,6900) (25800,6900) }
wall id=w_ea_d partition thickness 100 { (22800,6900) (22800,11400) }
# ---- the two departments ----------------------------------------------------
zone public "Public" {
room id=r_wait at (0,0) size 9000x6900 label "Waiting" uses hall
room id=r_recept at (0,6900) size 4500x4500 label "Reception" uses office
room id=r_entry at (4500,6900) size 4500x4500 label "Entrance" uses entry
}
zone clinical "Clinical" {
room id=r_corr at (9000,4500) size 16800x2400 label "Corridor" uses circulation
room id=r_treat at (19800,0) size 6000x4500 label "Treatment" uses office
room id=r_staff at (19800,6900) size 3000x4500 label "Staff Room" uses office
room id=r_wc at (22800,6900) size 3000x4500 label "Accessible WC" uses wc
# Three north, three south. The south row is `mirror y`, so its doors face the
# corridor; each instance also becomes a nested zone (`clinical.c4`), which is what
# lets `arch describe --json --select zones` report one consulting room on its own.
place consult(4500, 1) as c1 at (9000,0)
place consult(4500, 2) as c2 at (12600,0)
place consult(4500, 3) as c3 at (16200,0)
place consult(4500, 4) as c4 at (9000,11400) mirror y
place consult(4500, 5) as c5 at (12600,11400) mirror y
place consult(4500, 6) as c6 at (16200,11400) mirror y
}
# ---- the route through the building ------------------------------------------
# An automatic slider at the front door, then a cased opening into the waiting hall and
# a full-width opening into the corridor — no leaf anywhere on the patient's route.
door id=d_main sliding at (6750,11400) width 1800 wall shell slide right
opening id=o_entry on w_wb at 6750 width 2400
opening id=o_recept on w_wb at 900 width 1200
door id=d_recept on w_wb_d at 2250 width 900
opening id=o_corr on w_corr_w at 50% width 1800
# The treatment room takes a 1200 slider a trolley fits through; the accessible WC's
# leaf swings OUT into the corridor, which is why the corridor is dimensioned below.
door id=d_treat sliding on w_tr_s at 3000 width 1200 slide left
# `hinge near end`: the leaf swings toward the party wall, not back over the sink.
door id=d_staff on w_ea_n at 1500 width 1000 hinge near end
door id=d_wc on w_ea_n at 4500 width 1000 swing into r_corr
# ---- glazing -------------------------------------------------------------------
# One window per consulting room on each long facade, on the room's centre by
# construction: the units are on 3600 centres from x = 9000.
for i in 0..3 {
window at (10800 + i * 3600, 0) width 1200 wall shell
window at (10800 + i * 3600, 11400) width 1200 wall shell
}
window at (4500,0) width 3000 wall shell
window at (0,3000) width 2400 wall shell
window at (22800,0) width 1800 wall shell
window at (25800,2250) width 1200 wall shell
window at (2250,11400) width 1500 wall shell
window at (21300,11400) width 1200 wall shell
window at (24300,11400) width 900 wall shell
# ---- fit-out ---------------------------------------------------------------------
for i in 0..3 {
furniture seating at (1200, 1200 + i * 1800) size 2400x600 label "Seating"
furniture seating at (5400, 1200 + i * 1800) size 2400x600 label "Seating"
}
furniture id=f_recept counter against wall w_wb offset 3100 size 2400x600 in r_recept
furniture id=f_tr_counter counter against wall w_tr_s offset 1000 size 1800x600 in r_treat
furniture id=f_tr_basin basin against wall w_tr_s offset 5200 in r_treat
furniture id=f_tr_couch couch at (21000,1000) size 2000x800 label "Exam couch"
furniture id=f_st_sink kitchen_sink against wall w_mid_s offset 1200 in r_staff
furniture id=f_st_table table at (20400,9600) size 1600x900 label "Table"
furniture id=f_wc_pan wc against wall w_ea_d offset 3500 in r_wc
furniture id=f_wc_basin basin against wall w_ea_d offset 1200 in r_wc
# The dotted id is what makes ONE instance addressable: this trolley belongs to the
# second consulting room and to no other.
furniture id=f_ecg trolley in c2.main anchor bottom-right inset 300 size 700x500 label "ECG"
# The Entrance is otherwise a bare pass-through between the slider and the waiting hall.
furniture id=f_entry_plant plant at (8000,7200) size 600x600 in r_entry
# ---- the dimension that matters --------------------------------------------------
# `dim clear` pulls both endpoints in to the inner FACES, so this reads the corridor's
# clear width (2400 centre-to-centre less two half-partitions = 2300), not its nominal
# one. `offset 0` keeps the line on the measured axis: a clear width is measured where
# it is, so there is no outward side to push it to.
dim clear (11000,4500)->(11000,6900) offset 0 text "2300 clear"
title {
project "Outpatient clinic wing"
drawn_by "ArchLang"
date "2026-08-16"
}
}
Geometry & experiments
Gallery L (rooms that are not rectangles)
Two polygonal rooms — 2 rooms, 153 m² at scale 1:100: an L-shaped gallery wrapping a trapezoid lobby, so the building has an angled south-west facade. room polygon (x,y) … replaces at + size with the room's own ring, and everything downstream follows the ring rather than a bounding box — the gallery reports its exact 132 m² (its box would claim 168), the two rooms read as adjacent across the boundary they actually share, and the label sits at the polygon's centroid (the lobby pins its own with label "…" at (x,y)). The entrance door is hosted on the angled wall the ordinary way, and dims auto all measures the chain off the rooms' vertex coordinates.
# Polygonal rooms (v1.23): a floor whose spaces are not rectangles.
#
# `room polygon (x,y) …` gives a room an explicit, implicitly-closed ring instead of
# `at` + `size`. Two shapes a rectangle cannot express are drawn here:
#
# · gallery — an L, wrapping the entrance hall on two sides. Its area (132 m²) is the
# exact shoelace area of the ring, not its 12 × 14 m bounding box.
# · lobby — a trapezoid, so the building has an angled south-west facade. The joint
# where that facade meets the south wall is 40°, which is what the wall outline's
# mitre cap is there for.
#
# Everything else is ordinary: the walls, the doors and `dims auto` neither know nor
# care that a room is polygonal.
plan "Gallery L" {
units mm
grid 100
paper A3 landscape
north up
theme presentation
dims auto all
title { project "Polygon rooms" drawn_by "ArchLang" }
wall exterior thickness 300 {
(0,0) (12000,0) (12000,9000) (6000,14000) (0,14000) close
}
wall id=spine partition thickness 150 { (6000,8000) (6000,14000) }
wall id=north_face partition thickness 150 { (6000,8000) (12000,8000) }
room id=gallery polygon (0,0) (12000,0) (12000,8000) (6000,8000) (6000,14000) (0,14000)
label "Gallery"
room id=lobby polygon (6000,8000) (12000,8000) (12000,9000) (6000,14000)
label "Lobby" at (8200,10200) uses entry
# The entrance sits on the ANGLED south-west facade, so it opens into the trapezoid
# lobby — a door on a wall that is not axis-aligned, hosted the ordinary way.
door id=entry on exterior at 51% width 1500 swing in
door id=inner on spine at 50% width 1200 hinge left swing in
window id=north_light on exterior at 20% width 3000
# ---- fit-out ----------------------------------------------------------------
# Polygon rooms refuse the anchor forms (E_PLACE_POLY — a nine-point anchor
# needs a rectangle), so every piece here is an absolute `at`, kept clear of the
# L's missing quadrant (x 6000-12000, y 8000-14000 is the lobby's, not the
# gallery's) and of the two doors' swing quarter-discs.
furniture bench at (2000,2000) size 1800x500 in gallery
furniture bench at (8000,2000) size 1800x500 in gallery
furniture plant at (10500,6500) size 700x700 in gallery
furniture plant at (1000,12800) size 700x700 in gallery
furniture desk at (2000,11000) size 1800x800 label "Reception desk" in gallery
furniture chair at (4200,11500) size 500x500 in gallery
}
Hexagon Pavilion
Six trapezoidal galleries ringing a circular rotunda — 7 rooms, 122.79 m² on a 15 × 12 m hexagonal footprint. Every other example is built from rectangles, because that is what a floor plan usually is; this one is not rectangular anywhere, and it exists to show that polygon rooms, circular rooms and arc wall edges compose.
The hexagon is not regular, and that is the point. A regular hexagon of circumradius R carries four of its vertices at an irrational offset no grid can express, so its sloped facades never measure a round anything — set one out at R = 7500 on a 50 mm grid and each nominal 7500 edge comes back 7504. A drawing that calls that 7500 is lying, and one that calls it 7504 looks like a mistake. So this shell is built on a 3-4-5 triangle instead: every sloped facade steps 4500 across and 6000 down, which is exactly 7500, and every vertex lands on the grid. The drum in the middle is one wall of two semicircular arcs on a centreline of radius 3000 — the inner hexagon's inradius, so it is tangent to all six galleries' inner edges, which is what lets the rotunda be room circle … radius 3000 (exactly 28.27 m² = πR²) and counts each gallery as adjacent to it. The six cased openings are positioned on drum at <pct>, which walks the wall by arc length, so 8.333 % is 30° — square onto the middle of a gallery. Its palette comes from a single colour with theme from "#2f6f4e".
# A hexagonal garden pavilion: six trapezoidal galleries ringing a circular rotunda.
#
# Every other example in examples/ is built from rectangles, because that is what a
# floor plan usually is. This one is not rectangular ANYWHERE, and it exists to show
# that the two non-rectilinear room forms compose with a curved wall:
#
# room … polygon (x,y) … an explicit ring — exact shoelace area, label at
# the centroid, no bounding box anywhere in the answer
# room … circle at (cx,cy) radius a round floor, area measured as exactly piR^2
# arc (x,y) radius R a curved WALL EDGE, drawn as a true SVG/DXF arc
#
# THE HEXAGON IS NOT REGULAR, AND THAT IS THE POINT. A regular hexagon of circumradius R
# carries four of its vertices at y = +/- R*sqrt(3)/2 — an irrational number no grid can
# express — so its sloped facades never measure a round anything: set one out at R = 7500
# on a 50 mm grid and each nominal 7500 edge comes back 7504. A drawing that calls that
# 7500 is lying, and one that calls it 7504 looks like a mistake. So this shell is built
# on a 3-4-5 triangle instead. Every sloped facade steps 4500 across and 6000 down, which
# is exactly 7500, and every vertex lands on the grid:
#
# (16000,7000) (11500,1000) (5500,1000) (1000,7000) (5500,13000) (11500,13000)
#
# Four facades of 7500, two of 6000, 15000 x 12000 overall — and every number on the sheet
# is a whole millimetre a builder can set out with a tape.
#
# A second hexagon at exactly half scale sits inside it. The six galleries are the
# trapezoids BETWEEN the two: four points each, two outer vertices and the two inner ones
# behind them — 7500 or 6000 on the facade, half that on the inside, 3000 deep. Six radial
# spokes run along the joins, and a masonry drum fills the middle.
#
# The drum is the part worth reading twice. It is written as ONE wall of two semicircular
# `arc` edges (there is no close-an-arc form: a closed curve is the two halves it is), on
# a centreline of radius 3000 and 1200 thick. 3000 is not a free choice: it is the inner
# hexagon's INRADIUS, so the drum's centreline is the circle inscribed in that hexagon,
# touching all six of the galleries' inner edges. That is what lets the rotunda be
# `room circle … radius 3000` — the same centreline, which is the ordinary ArchLang
# convention that a room's boundary is its wall's centreline, and the reason each gallery
# counts as ADJACENT to the rotunda rather than 300 mm short of it. The outer face lands
# at 3600, past the inner hexagon's corners at 3354, so the drum swallows the galleries'
# inner edge instead of leaving a crescent of unwalled floor behind it, and
# `dim diameter rotunda` calls out a round phi 6000. The six cased openings are positioned
# `on drum at <pct>`, which walks the wall by ARC LENGTH — so 8.333% is 30 degrees, square
# onto the middle of a gallery.
#
# Three things a hexagon teaches about ANNOTATION.
#
# 1. `dims auto overall` is the only auto mode worth having here: it gives the building
# one bounding chain, one R leader per distinct arc and one phi per circular room.
# The room and wall chains have no axis to lie along on a 53-degree facade, so the
# two north facades are dimensioned BY HAND. Endpoint ORDER is what puts each line
# outside, because the offset runs along the LEFT normal of from->to — write either
# pair backwards and the line is drawn through the building (`W_DIM_INSIDE`, with a
# machine-applicable swap). And the OFFSET has to be bigger than it looks: the rule
# measures the drawn line's midpoint against the building's room-extents BOX, so on
# a splayed facade clearing the wall is not enough — 1800 is still inside the
# rectangle, 3000 is out in the margin where a dimension belongs.
# 2. Neither hand dim carries a `text` override, and neither needs one: the facade
# really is 7500, so that is what the drawing says.
# 3. A polygon room labels itself at its CENTROID, and here the centroid is the worst
# place available — a 3000-deep trapezoid whose middle is where the drum bulges past
# it — while the phi call-out is drawn ON the diameter, exactly where the rotunda's
# own name would go. So every room pins its name by hand with `label "…" at (x,y)`,
# out on the radius that bisects its own wedge, midway between the drum face and the
# facade. That is the one place in this file where the author knows something the
# centroid does not.
#
# The palette is derived from a single colour with `theme from "#2f6f4e"` — one hex value
# becomes the whole set of wall, poche, floor and annotation inks.
plan "Hexagon Pavilion" {
units mm
grid 50
scale 1:50
north up
dims auto overall
theme from "#2f6f4e"
# ---- shell: the outer hexagon ---------------------------------------------
# Walked from the east vertex clockwise on the page, so the six facades run
# 0-7500 north-east, 7500-13500 north, 13500-21000 north-west, 21000-28500 south-west,
# 28500-34500 south, 34500-42000 south-east. The openings below are positioned in those
# millimetres rather than percentages, because a hexagon of two edge lengths has no
# single percentage that means "the middle of a facade".
wall id=shell exterior thickness 300 {
(16000,7000)
(11500,1000)
(5500,1000)
(1000,7000)
(5500,13000)
(11500,13000)
close
}
# ---- the drum: one full circle, written as its two halves -----------------
wall id=drum partition thickness 1200 {
(11500,7000)
arc (5500,7000) radius 3000
arc (11500,7000) radius 3000
}
# ---- the six spokes -------------------------------------------------------
# Each runs from an inner hexagon vertex out to the outer vertex beyond it, so the
# galleries' side edges and these walls are the same six lines.
wall id=spoke_e partition thickness 200 { (12250,7000) (16000,7000) }
wall id=spoke_ne partition thickness 200 { (10000,4000) (11500,1000) }
wall id=spoke_nw partition thickness 200 { (7000,4000) (5500,1000) }
wall id=spoke_w partition thickness 200 { (4750,7000) (1000,7000) }
wall id=spoke_sw partition thickness 200 { (7000,10000) (5500,13000) }
wall id=spoke_se partition thickness 200 { (10000,10000) (11500,13000) }
# ---- rooms ----------------------------------------------------------------
room id=rotunda circle at (8500,7000) radius 3000 label "Rotunda" at (8500,8300) uses hall
room id=g_ne polygon (16000,7000) (11500,1000) (10000,4000) (12250,7000)
label "Reading" at (12250,4200) uses living
room id=g_n polygon (11500,1000) (5500,1000) (7000,4000) (10000,4000)
label "Studio" at (8500,2300) uses office
room id=g_nw polygon (5500,1000) (1000,7000) (4750,7000) (7000,4000)
label "Tea Room" at (4750,4200) uses dining
room id=g_sw polygon (1000,7000) (5500,13000) (7000,10000) (4750,7000)
label "Store" at (4750,9800) uses storage
room id=g_s polygon (5500,13000) (11500,13000) (10000,10000) (7000,10000)
label "Entrance" at (7600,11700) uses entry
room id=g_se polygon (11500,13000) (16000,7000) (12250,7000) (10000,10000)
label "Gallery" at (12250,9800) uses living
# ---- portals: six cased openings on the curve -----------------------------
opening id=p_ne on drum at 8.333% width 1400
opening id=p_n on drum at 25% width 1400
opening id=p_nw on drum at 41.667% width 1400
opening id=p_sw on drum at 58.333% width 1400
opening id=p_s on drum at 75% width 1400
opening id=p_se on drum at 91.667% width 1400
# ---- entrance and glazing -------------------------------------------------
# The entrance slides. A leaf on hinges would sweep a 1.4 m quarter-disc across the
# middle of the one room that has to stay clear; a `sliding` leaf sweeps nothing at all,
# so no swing rule applies to it and the entrance keeps its own floor.
door id=d_main sliding on shell at 31500 width 1400 slide left
window id=win_ne on shell at 3750 width 2400
window id=win_n on shell at 10500 width 2400
window id=win_nw on shell at 17250 width 2400
window id=win_sw on shell at 24750 width 2400
window id=win_se on shell at 38250 width 2400
# ---- dimensions -----------------------------------------------------------
dim diameter rotunda
dim (16000,7000)->(11500,1000) offset 3000
dim (5500,1000)->(1000,7000) offset 3000
# ---- loose furniture ------------------------------------------------------
# Polygon rooms refuse the anchor forms (E_PLACE_POLY — a nine-point anchor needs a
# rectangle), so every free-standing piece here is an absolute `at`. The two that back
# onto something use `against wall`, which stays legal on a polygon room because it
# measures from the WALL and not from the room.
#
# An axis-aligned piece in a wedge gallery has to be set out against a SLOPED wall, and
# its far corner is what reaches that wall first — not its edge. Both benches below sat
# 100-130 mm inside a wall solid until `W_FURNITURE_WALL_COLLISION` learned to measure a
# wall that is not axis-aligned; nothing had ever checked. Keep them west of where they
# were: f_bench clears the sloped south-east facade, f_seat clears spoke_se.
furniture id=f_desk counter against wall shell segment 1 offset 3000 size 3000x500 label "Work bench" in g_n
furniture id=f_shelf counter against wall spoke_w offset 1400 size 1200x600 label "Shelves" in g_sw
furniture id=f_read table at (13150,5550) size 900x900 label "Table" in g_ne
furniture id=f_tea table at (3850,5550) size 900x900 label "Tea" in g_nw
furniture id=f_bench bench at (12950,8450) size 1400x450 label "Bench" in g_se
furniture id=f_seat bench at (9450,12000) size 1400x450 label "Bench" in g_s
title {
project "Hexagon Pavilion"
drawn_by "ArchLang"
date "2026-08-16"
}
}
Terrace Row
Four terrace dwellings generated from one component, placed and mirrored — 16 rooms, 199.8 m², 16 doors, 18 windows across 22.2 × 9.6 m. A terrace is the same plan four times, so it is written once: component unit(w, d, gable) in its own coordinates from (0,0), then four place statements, alternating mirror x.
mirror x reflects about the instance's own origin, so a mirrored unit is placed at its far x edge. Alternating it is what makes a terrace a terrace: u1 and u2 meet local-x=w to local-x=w, so their bathrooms and kitchen runs back onto one shared party wall, and u2 and u3 meet local-x=0 to local-x=0. The consequence is that the row's two free gables are both the local x=0 wall — which is why gable is 1 for the end units and 0 for the middle two, and why the if inside the component puts a side window on that wall and nowhere else. The offsets are arithmetic rather than typing (widths is an array and the placements are a running sum of it), the 600 mm stagger is one coordinate, and three of the four room names are pinned with label "…" at (x,y) written in the component's coordinates — a pinned label is a point like any other, so place carries it through the frame and the mirrored units get the mirrored position for free. theme blueprint is a named theme base: a whole cyanotype palette from one word.
# A row of four terrace dwellings generated from ONE component, placed and mirrored.
#
# A terrace is the same plan four times, so it should be written once. `component` +
# `place` is how ArchLang says that, and this file is the smallest complete example of
# the pattern:
#
# component unit(w, d, gable) { … } one dwelling, authored from (0,0) in its
# OWN coordinates, parameterised by width
# place unit(W1, DEPTH, 1) as u1 at (…) an addressable INSTANCE — every id inside
# becomes `u1.<id>`, auto-ids restart per
# instance, so the four are order-independent
# … as u2 at (…) mirror x a real reflection: door swings, hinges and
# the whole fit-out come out mirror-image
#
# Three things about the row are worth reading closely.
#
# MIRRORING PAIRS THE UNITS. `mirror x` reflects about the INSTANCE's own origin, so a
# mirrored unit is placed at its FAR x edge — u2 is 5400 wide and placed at 10800, which
# puts it on 5400…10800. Alternating the mirror is what makes a terrace a terrace: u1 and
# u2 meet local-x=w to local-x=w, so their bathrooms and kitchen runs back onto one shared
# party wall, and u2 and u3 meet local-x=0 to local-x=0. The consequence to notice is that
# the row's two FREE gables are both the local x=0 wall — which is why `gable` is 1 for u1
# and u4 and 0 for the two in the middle, and why the `if` inside the component puts a side
# window on that wall and nowhere else.
#
# THE OFFSETS ARE ARITHMETIC, NOT TYPING. `widths` is an array and X2/X3/X4 are a running
# sum of it. `place` cannot go inside a `for` (the `as <name>` is a plain identifier, not
# an interpolation), so the four lines are unrolled — but change a width and every unit
# after it moves on its own.
#
# THE STAGGER IS ONE COORDINATE. Alternate units sit 600 further from the street, which on
# this sheet means u2 and u4 are placed at y = 0 and u1 and u3 at y = SETBACK. The party
# walls still coincide over everything but that 600, so each junction draws the short
# exterior return a sawtooth terrace actually has.
#
# WHY THE PLAN IS THIS PLAN. A row of four is drawn on ONE sheet, so the unit that survives
# is the one whose rooms are still legible at a quarter of the width — which is a drawing
# constraint, not an architectural one, and it is worth being honest that it drove the
# layout. Four rooms, not five: living and kitchen are one front room with the galley run
# on the party wall, the way a 5.4 m frontage is actually built. Names of one word. Nothing
# narrower than 1.8 m. The test is not "does it compile" but "does every room still say its
# own name inside its own walls" — so `arch describe --json` reporting four rooms per unit
# and the PNG showing sixteen unobstructed labels are the same check, run two ways. Three
# of the four names are therefore PINNED with `label "…" at (x,y)`, written in the
# COMPONENT's own coordinates: a pinned label is a point like any other, so `place` carries
# it through the frame, and the two mirrored units get the mirrored position for free. Get
# the name right in one unit and it is right in four.
#
# `theme blueprint` is a NAMED theme base — a whole cyanotype palette from one word, with
# no `theme { … }` block to maintain.
plan "Terrace Row" {
units mm
# 50, not 100: the partitions are 100 thick and the fit-out is anchored `flush` to
# their inner faces, which lands on half-thickness coordinates.
grid 50
north up
dims auto overall
theme blueprint
let DEPTH = 9000 # every unit is the same depth; only the width varies
let SETBACK = 600 # the sawtooth: alternate units step back from the street
let widths = [5400, 5400, 6000, 5400]
let W1 = widths[0]
let W2 = widths[1]
let W3 = widths[2]
let W4 = widths[3]
# Running sum of the widths — the left edge of each unit in plan coordinates.
let X1 = 0
let X2 = X1 + W1
let X3 = X2 + W2
let X4 = X3 + W3
# ---- one dwelling ---------------------------------------------------------
component unit(w, d, gable) {
let BACK = 4000 # the sleeping band, at the back of the plot
let HALLD = 1800 # the cross hall between the two bands
let BATHW = 2200
let BEDW = w - BATHW
let FRONT = d - BACK - HALLD
wall id=shell exterior thickness 250 { (0,0) (w,0) (w,d) (0,d) close }
wall id=w_bath partition thickness 100 { (BEDW,0) (BEDW,BACK) }
wall id=w_hall_n partition thickness 100 { (0,BACK) (w,BACK) }
wall id=w_hall_s partition thickness 100 { (0,BACK + HALLD) (w,BACK + HALLD) }
room id=bed at (0,0) size BEDW x BACK label "Bed" at (1400,800) uses bedroom
room id=bath at (BEDW,0) size BATHW x BACK label "Bath" at (BEDW + 1100,2100) uses bath utility
room id=hall at (0,BACK) size w x HALLD label "Hall" uses hall circulation
room id=living at (0,BACK + HALLD) size w x FRONT label "Living" at (w / 2,6900) uses living dining kitchen
# Front door off the street into the front room; the garden door is a slider, so no
# leaf sweeps the bedroom floor.
door id=front at (1700,d) width 950 wall shell swing into living
door id=rear sliding at (1200,0) width 1400 wall shell slide left
door id=d_bed on w_hall_n at 2300 width 800 swing into bed
# `on <wall> at <pos>` takes a literal position, so the one opening whose distance
# depends on the unit WIDTH is written as a point on the same wall's centreline.
door id=d_bath at (w - 1400,BACK) width 800 wall w_hall_n swing into bath
opening id=o_hall on w_hall_s at 900 width 1000
window id=win_liv at (600,d) width 900 wall shell
window id=win_liv2 at (w - 900,d) width 1000 wall shell
window id=win_bed at (BEDW - 450,0) width 800 wall shell
window id=win_bath at (w - 500,0) width 500 wall shell
# Only the two units with a free gable get a side window — the other two have a
# party wall there, and a window in a party wall is not a window.
if gable == 1 {
window id=win_gable at (0,2100) width 1200 wall shell
}
# Fit-out by anchor: every piece is measured from its room's backing wall FACE
# (`flush`), so nothing here writes a coordinate or a half-thickness.
furniture id=f_bed bed in bed anchor bottom-left flush inset 100 size 1400x1900 label "Bed"
furniture id=f_ward wardrobe in bed anchor right flush size 600x1200
furniture id=f_shower shower in bath anchor top-left flush size 900x900
furniture id=f_basin basin in bath anchor top-right flush size 600x450
furniture id=f_wc wc in bath anchor bottom-right flush size 400x700
furniture id=f_sofa sofa in living anchor bottom-left flush size 800x1800 label "Sofa"
# The galley run rides the party wall: `against` derives position, depth and the
# quarter-turn from the wall, and each catalogued fixture brings its own footprint.
furniture id=f_sink kitchen_sink against wall shell segment 1 offset 6300 in living
furniture id=f_stove stove against wall shell segment 1 offset 7200 in living
furniture id=f_fridge fridge against wall shell segment 1 offset 8100 in living
}
# ---- the row --------------------------------------------------------------
# A mirrored instance is placed at its FAR x edge, so u2 covers X2…X2+W2 and u4 covers
# X4…X4+W4. u1 and u4 carry the gable flag; u2 and u4 take the sawtooth's front row.
place unit(W1, DEPTH, 1) as u1 at (X1, SETBACK)
place unit(W2, DEPTH, 0) as u2 at (X2 + W2, 0) mirror x
place unit(W3, DEPTH, 0) as u3 at (X3, SETBACK)
place unit(W4, DEPTH, 1) as u4 at (X4 + W4, 0) mirror x
title {
project "Four-unit terrace"
drawn_by "ArchLang"
date "2026-08-16"
}
}
Scripting & composition
Relational (right-of / below)
4 rooms, 60 m² at scale 1:100, positioned relative to one another with right-of / below, align and gap — resolved to absolute coordinates by deterministic arithmetic in topological order, not an optimizer (see relational placement).
# Relational placement (v1.0): rooms positioned relative to one another with
# `right-of` / `left-of` / `below` / `above` (+ `align` and `gap`), resolved to
# absolute coordinates by pure arithmetic in dependency order. Only `living` is
# anchored with `at`; every other room derives its position from a neighbour, so
# changing the anchor or a size reflows the whole plan deterministically.
plan "Relational 1BR" {
units mm
grid 50
scale 1:100
north up
# Exterior shell (walls are always absolute — relational placement is rooms-only).
wall exterior thickness 200 { (0,0) (8000,0) (8000,7500) (0,7500) close }
room id=living at (0,0) size 5000x4000 label "Living"
room id=kitchen right-of living align top gap 0 size 3000x4000 label "Kitchen"
room id=bed below living align left gap 0 size 5000x3500 label "Bedroom"
room id=bath right-of bed align top gap 0 size 3000x3500 label "Bath"
door at (1000,0) width 1000 wall exterior hinge left swing in
window at (6500,0) width 1800 wall exterior
# Head against the south wall — the only edge of this room the bed's 1500 mm head can
# back onto, since the boundary it shares with the living room carries no wall.
furniture bed at (300,5400) size 1500x2000 label "Bed" rotate 180
# A handful of freestanding pieces in the two upper rooms — this file's only wall is
# the exterior shell (relational placement is rooms-only, so there are no partitions
# to write `against wall` on), so everything below is a plain `at`.
furniture sofa at (200,2900) size 1800x800 label "Sofa" in living
furniture coffee_table at (200,2000) size 900x500 in living
furniture dining_table at (5300,300) size 2400x1200 label "Table" in kitchen
furniture chair at (5600,1700) size 450x450 in kitchen
furniture chair at (6800,1700) size 450x450 in kitchen
# Overall width, outside face to outside face (`faces` projects each endpoint onto
# the wall it runs into, so it prints the true 8200 rather than the 8000 room grid).
dim faces (0,7500)->(8000,7500) offset 600
title {
project "Relational Layout Demo"
drawn_by "ArchCanvas"
date "2026-06-26"
}
}
Parametric (let + for)
3 rooms, 60 m², 3 doors, 3 windows — a row of repeated units generated from let bindings, a value-function, an array, a scoped set, a for loop and string interpolation. It is the demonstration that scripting expands at compile time into a fixed, deterministic drawing: there is no runtime, no clock and no randomness, so the row you see is exactly what the loop produced.
# Parametric plan (v0.8 scripting): a row of studio units generated with a
# `for` loop over a range, a value-function, an array indexed per unit, a scoped
# `set` rule, an `if`, and string-interpolated labels. Everything is derived
# from the constants — change COUNT and the whole row regenerates.
plan "Parametric — Studio Row" {
units mm
grid 50
scale 1:100
north up
# Plan-level constants (visible everywhere below — plan scope is global).
let WALL = 200
let W = 4000 # unit width
let H = 5000 # unit depth
let DOOR = 900
let WIN = 1600
let COUNT = 3 # number of units
# A value-function (pure closure) — area in square metres.
let aream2(w, h) = w * h / 1000000
# Per-unit names, indexed by the loop variable.
let names = ["Studio A", "Studio B", "Studio C"]
# Entrance doors swing outward throughout this plan (scoped default).
set door(swing: out)
for i in 0..COUNT {
let x = i * W
wall exterior thickness WALL { (x, 0) (x + W, 0) (x + W, H) (x, H) close }
room at (x, 0) size W x H label "{names[i]}"
furniture bed at (x + 300, 300) size 1500x2000 label "Bed"
furniture kitch at (x + W - 1900, 300) size 1600x600 label "Kitchen"
door at (x + W / 2, H) width DOOR wall exterior hinge left
window at (x + W / 2, 0) width WIN wall exterior
# The end unit carries a per-unit area dimension (computed by the function).
# Referenced to the outer face (y = H + WALL/2) so the extension lines start at
# the wall and read downward, away from the building.
if i == COUNT - 1 {
dim (x, H + WALL / 2)->(x + W, H + WALL / 2) offset 600 text "{aream2(W, H)} m² each"
}
}
# Overall run, dimensioned above the building: right-to-left so the offset lands
# ABOVE the row (outside), and referenced to the outer top face (y = -WALL/2).
dim (W * COUNT, 0 - WALL / 2)->(0, 0 - WALL / 2) offset 1300 text "{COUNT} units"
}
Accessible metadata
2 rooms, 24 m² — the smallest plan that carries accTitle and accDescr, which supply the SVG <title>/<desc> emitted by arch compile --accessible. They are metadata only: the default output is byte-identical without the flag. Omit them and the description is derived from describe()'s caption instead.
# Accessible metadata demo — `accTitle` / `accDescr`.
#
# These two plan-level keywords supply the accessible SVG `<title>` and `<desc>`
# used by `arch compile --accessible` (and `compile(src, { accessible: true })`).
# They OVERRIDE the defaults: `accTitle` replaces the plan name in `<title>`, and
# `accDescr` replaces the auto-derived describe() caption in `<desc>`. Default
# (non-accessible) output is unaffected — this is metadata only.
plan "Flat 2B" {
units mm
grid 50
north up
accTitle "Two-room flat — accessible floor plan"
accDescr "A small flat: a living room with the entrance, and a bedroom off it, joined by a single interior door."
wall exterior thickness 200 { (0,0) (6000,0) (6000,4000) (0,4000) close }
wall partition thickness 100 { (3500,0) (3500,4000) }
room id=r_living at (0,0) size 3500x4000 label "Living" uses living
room id=r_bed at (3500,0) size 2500x4000 label "Bedroom" uses bedroom
door id=d_main at (1500,0) width 1000 wall exterior hinge left swing in
opening id=o_int at (3500,2000) width 900 wall partition
window at (5000,4000) width 1200 wall exterior
# ---- fit-out ----------------------------------------------------------------
# Kept clear of the entrance door's swing quarter-disc (radius 1000, hinge left
# at x=1000) and of the interior opening.
furniture sofa at (300,3100) size 1600x700 in r_living
furniture coffee_table at (400,2300) size 600x400 in r_living
furniture plant at (2900,3200) size 500x500 in r_living
# The bed sits clear of the interior opening (x 3550-4300 stays a 750 mm-wide
# entry corridor into the room), so the accessible route through it keeps its
# 700 mm minimum.
furniture bed at (4300,100) size 1500x2000 in r_bed
furniture nightstand at (4300,2150) size 450x400 in r_bed
furniture table at (4300,3100) size 900x700 label "Table" in r_bed
}
Style
Themed (blueprint + brick)
2 rooms, 24 m² with a custom theme { … } block and a brick wall material (hatch) — the same geometry, restyled. Nothing about a theme touches geometry, which is why the playground's theme selector can restyle any plan on this page. See theming and materials.
# Theming + materials: a two-room plan with a brick exterior, a partition,
# and a dark "blueprint" theme. Try CompileOptions.theme to override at runtime.
plan "Themed Duplex" {
units mm
grid 50
scale 1:100
north up
theme {
background: "#1e2127"
wall: "#e8e8e8"
wallFill: "#3a3f4b"
wallHatch: "#5a6172"
room: "#272b33"
roomLabel: "#f0f0f0"
areaLabel: "#9aa0aa"
furniture: "#2f343d"
dim: "#6cb6ff"
annotation: "#cfd3da"
font: "Georgia, serif"
lineWeight: 1.3
}
let W = 6000
let H = 4000
wall exterior thickness 250 material brick { (0,0) (W,0) (W,H) (0,H) close }
wall partition thickness 100 { (3000,0) (3000,H) }
room id=a at (0,0) size 3000x4000 label "Living"
room id=b at (3000,0) size 3000x4000 label "Bedroom"
furniture bed at (3300,300) size 1500x2000 label "Bed"
furniture nightstand at (4850,700) size 300x350 in b
furniture desk at (300,300) size 1200x600 in a
furniture office_chair at (900,1000) size 450x450 in a
door at (3000,2000) width 900 wall partition hinge left swing in
window at (1500,0) width 1600 wall exterior
window at (4500,0) width 1200 wall exterior
# `faces` measures outside-to-outside: 6000 of room grid + half the 250 shell at
# each end = the 6250 the building actually is.
dim faces (0,H)->(W,H) offset 600
}
Materials (the whole hatch palette)
A 16 × 8 m maker-space — 4 rooms, 128 m², 5 doors, 6 windows, paper A3 landscape at scale 1:50 — whose walls carry the entire wall-material palette, one per side. Poché is not decoration: on a real drawing the hatch inside a wall is the specification, and ArchLang spells that as one optional clause on wall: material poche|concrete|brick|insulation|tile|none [scale <n>] [angle <deg>].
All six appear here, each on the element a builder would actually detail that way and each on a wall long enough to read: 300 mm concrete on the two long facades (the thickest thing in the building, because that is what carries the roof), 230 mm brick on the east gable with scale 1.5 angle 90 so the courses run along the wall instead of across it, a 200 mm insulation leaf drawn as its own wall so the leaf rather than the assembly carries the hatch, tile on the two WC partitions, none on the glazed office screen (deliberately empty rather than defaulted), and the default poché on the two service partitions. The sheet is what makes it readable, and it is the second thing this file is for: at 1:50 a 100 mm partition draws 2 mm wide and a 300 mm facade 6 mm; halve that to 1:100 and they become 1 mm and 3 mm — the walls still look right and the hatch inside them is gone. A material sampler has to be issued at a scale where the poché survives, so the scale is written down rather than left to the auto-fit. The legend beside it is derived: one swatch per material actually used and one per fixture category actually placed, growing a row on its own if you add a seventh.
# A 16 x 8 m maker-space whose walls are the whole wall-material palette, one per side.
#
# Poche is not decoration. On a real drawing the hatch inside a wall IS the specification:
# it says concrete here, brickwork there, an insulated leaf in that facade, tiling round
# the wet room, and a frameless glazed screen where there is no wall at all. ArchLang
# spells that as one optional clause on `wall`:
#
# material poche|concrete|brick|insulation|tile|none [scale <n>] [angle <deg>]
#
# All six appear below, each on the element a builder would actually detail that way, and
# each on a wall long enough to read — a hatch you cannot see is a specification you have
# not written:
#
# concrete 300 the two long facades — the structural shell, and the thickest thing
# in the building because that is what carries the roof
# brick 230 the east gable, `scale 1.5 angle 90`: one brick-and-a-bit on plan,
# with the courses turned to run ALONG the wall instead of across it
# insulation 200 the west facade's insulated leaf, drawn as its own wall so that the
# leaf — not the assembly it belongs to — is what carries the hatch
# tile 100 the two WC partitions, the only walls in the building that get tiled
# none 100 the office screen: glazed, so it stops sound and not sight, and its
# poche is deliberately EMPTY rather than defaulted
# (omitted) 150 the two service partitions, which take the default poche hatch
#
# THE SHEET IS WHAT MAKES THIS READABLE, and it is the second thing this file is for.
# `paper A3 landscape` + `scale 1:50` is an operative pair: every annotation becomes a
# fixed number of millimetres ON THE PAPER (3.5 mm labels, 0.5 mm lines) times the scale
# denominator, and — the point here — a 100 mm partition is drawn 2 mm wide and a 300 mm
# facade 6 mm. Halve that to 1:100 — the auto-fit's answer on a smaller sheet — and they
# become 1 mm and 3 mm: the walls still look right, and the hatch inside them is gone.
# A material sampler is a drawing that has to be issued at a scale where the poche
# survives, so the scale is written down rather than left to the auto-fit.
#
# `legend` is what makes the sampler legible from the sheet alone: one swatch per material
# actually used and one per fixture category actually placed, all derived — there is
# nothing to configure and nothing to keep in step by hand. Add a wall in a seventh
# material and the table grows a row on its own. `schedule rooms` beside it is derived the
# same way: its NO./NAME/AREA rows and its total are the rooms below, and they are the
# same numbers `arch describe --json` reports under `schedule[]`.
plan "Workshop" {
units mm
# 10, not 50: a 230 brick wall has a 115 half-thickness, and the WC fixtures are placed
# `flush` against wall FACES — so the grid has to be able to express a half-thickness of
# every wall a fixture backs onto, or the snap pushes the piece into the wall it is
# supposed to sit against.
grid 10
paper A3 landscape
scale 1:50
north up
dims auto all
schedule rooms
legend
style wall { stroke: "#1f2a24" }
style room { fill: "#f6f4ef" }
# ---- shell: four walls, four specifications ------------------------------
wall id=w_north exterior thickness 300 material concrete { (0,0) (16000,0) }
wall id=w_south exterior thickness 300 material concrete { (0,8000) (16000,8000) }
wall id=w_east exterior thickness 230 material brick scale 1.5 angle 90 { (16000,0) (16000,8000) }
wall id=w_west exterior thickness 200 material insulation { (0,0) (0,8000) }
# ---- the service strip ----------------------------------------------------
# Five partitions, three specifications: a glazed screen to the office, ordinary
# blockwork to the store, tiling to the WC.
wall id=p_screen partition thickness 100 material none { (12000,0) (12000,3000) }
wall id=p_spine partition thickness 150 { (12000,3000) (12000,5800) }
wall id=p_wc_w partition thickness 100 material tile { (12000,5800) (12000,8000) }
wall id=p_off_s partition thickness 150 { (12000,3000) (16000,3000) }
wall id=p_wc_n partition thickness 100 material tile { (12000,5800) (16000,5800) }
# ---- rooms ----------------------------------------------------------------
room id=r_shop at (0,0) size 12000x8000 label "Workshop" uses utility
room id=r_office at (12000,0) size 4000x3000 label "Office" uses office
room id=r_store at (12000,3000) size 4000x2800 label "Store" uses storage
room id=r_wc at (12000,5800) size 4000x2200 label "WC" uses wc
# ---- openings -------------------------------------------------------------
# The shop door slides: a 2.4 m leaf on hinges would sweep a quarter of the aisle, and a
# sliding leaf sweeps nothing at all, so no swing rule applies to it.
door id=d_shop sliding on w_south at 3000 width 2400 slide left
door id=d_ped on w_south at 6600 width 1000 swing into r_shop
door id=d_office on p_screen at 1500 width 900 swing into r_office
door id=d_store on p_spine at 1400 width 900 swing into r_store
door id=d_wc on p_wc_w at 1100 width 800 swing into r_wc
window id=win_n1 on w_north at 2000 width 2400
window id=win_n2 on w_north at 6000 width 2400
window id=win_n3 on w_north at 10000 width 2400
window id=win_w on w_west at 4000 width 1800
window id=win_o on w_east at 1500 width 1600
window id=win_wc on w_east at 6900 width 600
# ---- fit-out --------------------------------------------------------------
# Every catalogued fixture backs onto a wall by construction: the offset runs ALONG the
# named wall, the depth comes off its face, and the quarter-turn is derived from the
# side `in <room>` picks. Nothing here writes a coordinate or a rotation.
furniture id=f_sink sink against wall w_north offset 5000 in r_shop
furniture id=f_bench1 counter against wall w_west offset 2000 size 3000x700 label "Bench" in r_shop
furniture id=f_bench2 counter against wall w_west offset 6200 size 2500x700 label "Bench" in r_shop
furniture id=f_bench3 counter against wall w_north offset 9500 size 3000x700 label "Bench" in r_shop
furniture id=f_bench4 counter against wall w_south offset 9500 size 3000x700 label "Bench" in r_shop
furniture id=f_rack1 racking at (1600,5600) size 2600x600 label "Racking" in r_shop
furniture id=f_table table at (3400,3300) size 2600x1300 label "Assembly table" in r_shop
furniture id=f_saw table at (7600,5400) size 2000x1000 label "Bench saw" in r_shop
furniture id=f_desk desk at (13800,700) size 1600x800 label "Desk" in r_office
furniture id=f_ochair office_chair at (14600,1700) size 500x500 in r_office
furniture id=f_rug rug at (13000,300) size 2900x2400 in r_office
furniture id=f_racks racking at (12300,3300) size 3400x600 label "Racking" in r_store
furniture id=f_wc wc in r_wc anchor bottom-right flush size 400x700
furniture id=f_basin basin in r_wc anchor top-right flush size 600x450
title {
project "Maker-space workshop"
drawn_by "ArchLang"
date "2026-08-16"
}
}