Floor plans
layout: floorplan is the drawing engine under an architect’s vocabulary: the
same datum, anchors and dimensions, with walls, openings and fixtures in place
of parts and features.
Walls and the openings in them
{ layout: floorplan; unit: m; scale: 0.02; }
|wall#outer| {
draw: move(0, 0) right(7.2):north down(4.8):east left(7.2):south close():west;
} [
|window| { on: north; at: 2.7; width: 1.6; }
|door#entry| { on: south; at: 1.05; width: 0.95; swing: right; }
|door| { on: west; at: 1.4; width: 2.4; symbol: sliding; }
]
|partition#bathwall| {
draw: move(4.9, 0) down(2.3):face right(2.3):side;
} [
|door| { on: side; at: 0.15; width: 0.8; }
]
A |wall|’s draw: traces the centreline. thickness: — 200 mm by
default, 100 for a |partition| — grows that line into the mitred outline that
takes the paint.
unit: m makes one drawing unit a metre, and scale: 0.02 is the 1:50 ratio.
An opening rides in its wall’s [ ], stationed on: a named segment at: a
distance from its start. The gap clips the wall at the jambs, and each type
draws its own chrome:
|door|— a leaf and a quarter swing arc, or two panels forsymbol: sliding.|window|— a sill pair.
The entry states no hinge: because every built-in size and default is real: a
door is 900 mm and hangs on its segment’s near jamb.
Mind the draw direction, twice. Both of these read from the pen, not from the picture:
at:counts from the segment’s start. The entry’s 1.05 is measured from the east end, becausesouthwas drawn leftward.swing:names the left of the pen’s travel — which on that same leftward run is the outside.rightis what opens the door into the flat.
The bathroom’s own wall ends at the shell rather than crossing it, which is what keeps a partition out of the middle of an opening.
Fixtures and room text
{
layout: floorplan; unit: m; scale: 0.02;
// a furniture plan tints what it is about, and leaves the shell alone
|bed| { fill: --purple-wash; stroke: --purple-deep; }
|sofa| { fill: --purple-wash; stroke: --purple-deep; }
|dining| { fill: --rose-wash; stroke: --rose-deep; }
|bath| { fill: --sky-wash; stroke: --sky-deep; }
|appliance| { fill: --sky-wash; stroke: --sky-deep; }
}
|wall#outer| {
draw: move(0, 0) right(7.2):north down(4.8):east left(7.2):south close():west;
} [
|window| { on: north; at: 2.7; width: 1.6; }
|door#entry| { on: south; at: 1.05; width: 0.95; swing: right; }
]
|partition#bathwall| {
draw: move(4.9, 0) down(2.3):face right(2.3):side;
} [
|door| { on: side; at: 0.15; width: 0.8; }
]
|bed| { rotate: 90; translate: 1.15 1.05; }
|sofa| { symbol: two; rotate: 90; translate: 0.6 3.4; }
|dining| { symbol: round; translate: 3.3 3.3; }
|rect#counter| { width: 0.6; height: 1.4; translate: 6.8 3.2;
fill: --bg; stroke: --stroke-dark; stroke-width: 1; }
|appliance| "F" { symbol: fridge; translate: 6.8 2.8; }
|bath| { symbol: shower; translate: 6.6 0.6; }
|bath| { symbol: toilet; rotate: 90; translate: 5.35 0.5; }
|bath| { symbol: sink; rotate: 90; translate: 6.85 1.6; }
"STUDIO 27 m²" { translate: 2.2 2.2; }
Six symbol-bodied types carry the furniture — |bed|, |sofa|, |dining|,
|bath|, |appliance|, |stairs| — and symbol: picks the variant, so one
type draws the tub, the toilet and the basin alike.
Their bodies are true millimetres, which is why not one of them states a
size. They place with translate: from the datum like any drawing child, and
they mask what they sit on.
rotate: turns a piece to its wall, and the reference is the unturned pose: a
toilet backs west at 0°, so 90° stands it against a north wall.
Two things that are not special types:
- A room name is plain sheet text. There is no room type, because a room is what the walls already enclose.
- A counter is a plain
|rect|filled--bg, so it masks the floor exactly as a fixture does.
The tint is not part of the family either. Five type rules make this a furniture plan — sleeping and seating in purple, dining in rose, plumbing and white goods in blue — while the shell stays the black poché a plan expects.
Dimensioning to the faces
|floorplan#flat| "UNIT 3B" { unit: m; scale: 0.02; } [
|wall#outer| {
draw: move(0, 0) right(7):north down(4.6):east left(7):south close():west;
} [
|door#entry| { on: east; at: 3.4; width: 0.95; swing: right; }
]
|partition#bedwall| { draw: move(2.8, 0) down(4.6):side; } [
|door| { on: side; at: 1.9; width: 0.8; }
]
outer:west-in (-) bedwall:side-out { side: top; }
bedwall:side-in (-) outer:east-in { side: top; }
outer:west-in (-) outer:east-in { side: top; }
outer:north-in (-) outer:south-in { side: right; }
]
Every named wall run derives two face anchors — its offset edges:
name-in— the enclosed side on a closed loop, or the left of the pen’s travel on an open one.name-out— the other face.
That distinction is what a plan publishes: a room’s clear span, face to face. Never a centreline, and never a span that runs through a wall.
bedwall:side was drawn southward, so its -out face is the western one — the
left-hand room’s — and its -in face the eastern one. Read them the other way
round and each dimension eats the partition: the two would sum past the overall
instead of 2.65 + 0.1 + 4.05 = 6.8.
Dimensions sharing a side pack into rows, which is what keeps the two room
clears inside the shell’s own overall. |floorplan| is the container form, and
its label lowers to a title under the plan.
The whole parts library
{
density: 4; font-size: 19;
|head::block| {
layout: flow; align: center; font-size: 22; letter-spacing: 1;
}
|cap::block| { layout: flow; align: center; color: --muted; }
}
|floorplan#parts| { unit: m; scale: 0.02; } [
// ── Walls. A `|wall|`'s `draw:` is the **centreline**; `thickness:` offsets
// it half either way into the outline that takes the paint — mitred at the
// corners, bevelled past miter limit 4, concentric round an `arc()`.
|head| { translate: -3.7 0; } [ "WALLS" ]
|wall| { draw: move(-1.2, 0) right(2.4); }
|partition| { draw: move(1.5, 0) right(2.4); }
|wall| { draw: move(4.2, 0) right(2.4); thickness: 0.4; }
|wall| {
draw: move(6.9, 0) right(2.4);
fill: --bg; stroke: --stroke-dark; stroke-width: 1;
}
|wall| {
draw: move(9.6, 0) right(2.4);
fill: hatch(45); stroke: --stroke-dark; stroke-width: 1;
}
|wall| { draw: move(12.9, -0.5) right(1.2) down(1); }
|wall| { draw: move(15.1, 0.25) arc(2.2, 0, 1.8); }
|cap| { translate: 0 1.3; } [ "solid · 200" ]
|cap| { translate: 2.7 1.3; } [ "|partition| · 100" ]
|cap| { translate: 5.4 1.3; } [ "thickness: 0.4" ]
|cap| { translate: 8.1 1.3; } [ "fill: --bg" ]
|cap| { translate: 10.8 1.3; } [ "hatch(45) section" ]
|cap| { translate: 13.5 1.3; } [ "mitred corner" ]
|cap| { translate: 16.2 1.3; } [ "arc · concentric" ]
// ── Doors. `hinge:` picks the jamb by the segment's draw direction and
// `swing:` the side the leaf opens toward — `left` is the left of the pen's
// travel, so on these east-running walls it opens north. The chrome is
// generated: a leaf at 90° open plus its quarter arc, two mirrored halves for
// `double`, and two overlapping panels for `sliding`, which hangs no leaf. An
// opening's own text is its **schedule tag**, seated beside the gap on the
// face the leaf never sweeps — the last cell wears one.
|head| { translate: -3.7 2.9; } [ "DOORS" ]
|wall| { draw: move(-1.2, 2.9) right(2.4):run; } [
|door| { on: run; at: 0.75; hinge: start; swing: left; }
]
|wall| { draw: move(1.5, 2.9) right(2.4):run; } [
|door| { on: run; at: 0.75; hinge: start; swing: right; }
]
|wall| { draw: move(4.2, 2.9) right(2.4):run; } [
|door| { on: run; at: 0.75; hinge: end; swing: left; }
]
|wall| { draw: move(6.9, 2.9) right(2.4):run; } [
|door| { on: run; at: 0.75; hinge: end; swing: right; }
]
|wall| { draw: move(9.6, 2.9) right(2.4):run; } [
|door| { on: run; at: 0.45; width: 1.5; symbol: double; }
]
|wall| { draw: move(12.3, 2.9) right(2.4):run; } [
|door| { on: run; at: 0.3; width: 1.8; symbol: sliding; }
]
|partition| { draw: move(15, 2.9) right(2.4):run; } [
|door| "D1" { on: run; at: 0.75; }
]
|cap| { translate: 0 4.25; } [ "start · left" ]
|cap| { translate: 2.7 4.25; } [ "start · right" ]
|cap| { translate: 5.4 4.25; } [ "end · left" ]
|cap| { translate: 8.1 4.25; } [ "end · right" ]
|cap| { translate: 10.8 4.25; } [ "symbol: double" ]
|cap| { translate: 13.5 4.25; } [ "symbol: sliding" ]
|cap| { translate: 16.2 4.25; } [ "|partition| · schedule tag" ]
// ── A window draws its sill pair across the gap at the thickness's thirds;
// openings share a run freely as long as they never overlap. A flight draws
// `steps` treads at a 250 mm going, plus the up arrow.
|head| { translate: -3.7 6; } [ "WINDOWS · STAIRS" ]
|wall| { draw: move(-1.2, 6) right(2.4):run; } [
|window| { on: run; at: 0.6; }
]
|wall| { draw: move(1.5, 6) right(2.4):run; } [
|window| { on: run; at: 0.2; width: 2; }
]
|partition| { draw: move(4.2, 6) right(2.4):run; } [
|window| { on: run; at: 0.6; }
]
|wall| { draw: move(6.9, 6) right(2.4):run; } [
|door| { on: run; at: 0.2; width: 0.7; }
|window| { on: run; at: 1.2; width: 1; }
]
// the two flights stand on the row's baseline, so every caption in the row
// sits on one line however tall the piece above it is
|stairs| { steps: 4; translate: 10.8 6.6; }
|stairs| { steps: 8; translate: 13.5 6.1; }
|cap| { translate: 0 7.35; } [ "1200 default" ]
|cap| { translate: 2.7 7.35; } [ "width: 2" ]
|cap| { translate: 5.4 7.35; } [ "in a |partition|" ]
|cap| { translate: 8.1 7.35; } [ "two on one run" ]
|cap| { translate: 10.8 7.35; } [ "steps: 4" ]
|cap| { translate: 13.5 7.35; } [ "steps: 8" ]
// ── The furniture. Every body is one path on the fixture's own node, filled
// `--bg` so it masks the floor under it; the smart label reads **beside** the
// body, like a discrete's value — so a row seats its pieces on one baseline
// and their labels line up without a caption being written at all. A bed is
// named for its mattress: `queen` is what a bare `|bed|` draws.
|head| { translate: -3.7 9.4; } [ "BEDS" ]
|bed| "queen" { translate: 0 9.4; }
|bed| "king" { symbol: king; translate: 2.7 9.4; }
|bed| "double" { symbol: double; translate: 5.4 9.45; }
|bed| "single" { symbol: single; translate: 8.1 9.4; }
// One anatomy at four widths — outline, then the run tracing arm → back →
// arm; `one` is the armchair, `corner` folds the same two strokes round an L.
// The bar `stool` is the family's one-stroke member: a plain round seat.
|head| { translate: -3.7 12.2; } [ "SOFAS" ]
|sofa| "three" { translate: 0 12.95; }
|sofa| "two" { symbol: two; translate: 2.7 12.95; }
|sofa| "one" { symbol: one; translate: 5.4 12.95; }
|sofa| "corner" { symbol: corner; translate: 8.1 12.2; }
|sofa| "stool" { symbol: stool; translate: 10.8 13.225; }
// A `|dining|` is sized by its **tabletop**; the chairs stand a small
// pull-back off its edge, and that pair of rows is what extends the bbox.
|head| { translate: -3.7 15.4; } [ "DINING" ]
|dining| "six" { translate: 0 15.445; }
|dining| "four" { symbol: four; translate: 2.7 15.495; }
|dining| "round" { symbol: round; translate: 5.4 15.395; }
// The sanitaryware. A toilet is **one** silhouette — the cistern's shoulders
// flowing into the rounded pan — and `double-sink` is one unit with two
// basins, the kitchen run's, never two sinks side by side.
|head| { translate: -3.7 17.45; } [ "BATH" ]
|bath| "tub" { translate: 0 17.525; }
|bath| "shower" { symbol: shower; translate: 2.7 17.45; }
|bath| "toilet" { symbol: toilet; translate: 5.4 17.7; }
|bath| "sink" { symbol: sink; translate: 8.1 17.7; }
|bath| "double-sink" { symbol: double-sink; translate: 10.8 17.675; }
// ── …and the kitchen boxes, whose label is the one that centres **in** the
// body — the F / W / DW convention every real plan writes.
|head| { translate: -3.7 18.9; } [ "APPLIANCES" ]
|appliance| { symbol: stove; translate: 0 18.9; }
|appliance| "F" { symbol: fridge; translate: 2.7 18.9; }
|appliance| "W" { symbol: washer; translate: 5.4 18.9; }
|appliance| "DW" { symbol: dishwasher; translate: 8.1 18.9; }
|cap| { translate: 0 19.55; } [ "stove" ]
|cap| { translate: 2.7 19.55; } [ "fridge" ]
|cap| { translate: 5.4 19.55; } [ "washer" ]
|cap| { translate: 8.1 19.55; } [ "dishwasher" ]
]
Every wall look, every door pose and symbol, the windows, the flights, and all
six fixture families with every symbol: variant — drawn at 1:50, so the sizes
here are the real ones.
A floorplan is a drawing scope, so there is no track grid to lay a catalogue
on: every piece is datum-placed with translate: and every wall is drawn where
it stands. The true-size law does the rest — a tub is 1700 × 750 mm and a door
left to itself is 900 mm wide, whatever the file drafts in.
Printed as a blueprint
{
density: 3; clearance: 16; padding: 40;
fill: #00509e; color: #edf5fb;
--stroke-dark: white; --stroke-light: rgba(255, 255, 255, 0.72);
--caption-color: rgba(255, 255, 255, 0.72);
--footer-color: rgba(255, 255, 255, 0.72);
// every body that masks the floor takes the paper too
|slab::rect| { fill: #00509e; stroke: --stroke-dark; stroke-width: 1; }
|bed| { fill: #00509e; }
|sofa| { fill: #00509e; }
|dining| { fill: #00509e; }
|bath| { fill: #00509e; }
|appliance| { fill: #00509e; }
|room::block| {
layout: flow; direction: column; align: center; gap: 2;
font-size: 11; letter-spacing: 1;
}
}
|floorplan#unit| "UNIT 12A — ONE BEDROOM · 65 m²" { unit: m; scale: 0.02; } [
|wall#outer| {
draw: move(0, 0) right(9.6):north down(6.8):east left(9.6):south
close():west;
} [
|door#entry| "D1" { on: east; at: 1.2; width: 0.95; swing: right; }
|door#slider| "D2" { on: west; at: 3.4; width: 2.4; symbol: sliding; }
|window#w1| "W1" { on: west; at: 0.5; width: 2; }
]
// north and south are party walls against the neighbouring units, so they
// carry no openings at all — every one a condo unit gets is on the entry
// side or the balcony side
|partition#bedwall| { draw: move(0, 3.8) right(4.6):head down(3):side; } [
|door#bed-door| "D3" { on: side; at: 0.4; width: 0.9; swing: right; }
]
|partition#laundry| { draw: move(6, 6.8) up(0.8); }
// a stub off the south wall: with the bathroom's own west wall it makes the
// alcove the stacked washer/dryer is built into
|partition#bathwall| { draw: move(9.6, 4) left(2.9):head down(2.8):side; } [
|door#bath-door| "D4" { on: side; at: 1; width: 0.8; hinge: end; }
]
// Casework is plain geometry: counters, the island, the wardrobe, the deck.
|slab#deck| { width: 1.4; height: 6.6; translate: -0.8 3.4; }
|slab| { width: 3.9; height: 0.6; translate: 7.55 0.4; }
|slab| { width: 2; height: 0.9; translate: 7.2 2.05; radius: 4; }
|slab| { width: 1.1; height: 0.55; translate: 2 1.75; }
|slab| { width: 0.45; height: 0.45; translate: 0.6 4.075; }
|slab| { width: 0.45; height: 0.45; translate: 3 4.075; }
|slab| { width: 0.6; height: 1.4; translate: 4.25 6; }
|slab| { width: 1.2; height: 0.55; translate: 7.35 4.325; }
|appliance| { symbol: stove; translate: 6.4 0.4; }
|appliance| "F" { symbol: fridge; translate: 8.1 0.4; }
|appliance| "DW" { symbol: dishwasher; translate: 7.7 2.05; }
|appliance| "W/D" { symbol: washer; translate: 6.35 6.4; }
|bath| { symbol: double-sink; translate: 6.8 2.05; }
|sofa| { symbol: stool; translate: 6.7 2.75; }
|sofa| { symbol: stool; translate: 7.2 2.75; }
|sofa| { symbol: stool; translate: 7.7 2.75; }
|sofa| { translate: 2 0.55; }
|dining| { symbol: four; translate: 4.3 2.2; }
|bed| { translate: 1.8 4.85; }
|bath| { symbol: sink; translate: 7.35 4.325; }
|bath| { symbol: toilet; rotate: 180; translate: 9.15 5; }
|bath| { symbol: tub; translate: 7.6 6.325; }
|bath| { symbol: shower; translate: 9.05 6.25; }
|room| { translate: 2.2 2.75; } [ "LIVING / DINING" "23.9 m²" ]
|room| { translate: 7.4 3.2; } [ "KITCHEN" ]
|room| { translate: 3.3 6.1; } [ "BEDROOM" "12.8 m²" ]
|room| { translate: 5.65 4.6; } [ "HALL" ]
|room| { translate: 8.35 5.55; } [ "BATH" "7.3 m²" ]
|room| { translate: -0.8 3.4; } [ "BALCONY" ]
// The north arrow a plan needs is a |sketch| define — the parts-library
// escape, since the pen already draws it.
|sketch#north| {
draw: move(-0.8, 5.73) line(0.22, 0.72) line(-0.22, -0.2) line(-0.22, 0.2)
close();
fill: --stroke-dark; stroke: none;
}
|room| { translate: -0.8 5.46; } [ "N" ]
outer:west-in (-) outer:east-in { side: top; }
outer:west-in (-) bedwall:side-out { side: bottom; }
bedwall:side-in (-) outer:east-in { side: bottom; }
outer:north-in (-) bathwall:head-out { side: right; }
bathwall:head-in (-) outer:south-in { side: right; }
outer:north-in (-) outer:south-in { side: right; }
]
The plan is the one the reference implementation ships as samples/floorplan.lini,
untouched below the stylesheet — what makes it a print is the handful of visual
variables above it. --stroke-dark carries the poché and every dimension line,
--stroke-light the thin support lines, and the two caption tones follow. The
paper is stated as a literal rather than through --bg, because --bg names
the host page’s backdrop and a page that owns it would hand this figure white;
the fixtures take the same literal, since each one masks the floor with the
paper it sits on. The whole recipe is a built-in — lini --theme blueprint
prints any figure this way.
Go deeper
Floorplan — the architectural dialect covers wall junctions, hinge control, stairs and the full fixture table; the rest of section 15 is the engine underneath it.