loft grammar cheat-sheet (v0.4)

One statement per line. # starts a comment — comments are kept (they ride the statement below them, or the line they end, and survive reformatting). Blank lines are ignored.

Type declarations may also take the block form: end the head with : and put one parameter per indented line beneath it (any consistent indent). Same model either way — the one-line form stays legal, and the formatter picks.

Statement order across the file never matters — references resolve across the whole file. Within a line, slot order is fixed: category → type → [id] → placement (see Line anatomy).

A model may span multiple .loft files: every file in a project directory compiles as ONE model with ONE namespace — reference names across files bare, with no import lines, and file layout never changes meaning (folders navigate, never scope).

Start here — build in this order

Everything below this section is reference. This section is method. Most broken models are correctly-spelled statements written in the wrong order and measured from the wrong thing.

# 1. datums first — the framework everything else is measured from
level L1 elev 0' height 10'-0"
grid A vertical   at 0'
grid B vertical   at A + 24'-0"         # the bay width, not grid B's coordinate
grid C vertical   at B + 30'-0"
grid 1 horizontal at 0'
grid 2 horizontal at 1 + 20'-0"

# 2. then hosts — walls today (floors and roofs later); frame first, then the line
# traveling (A,1) -> (C,1), heading east; right = south
wall EXT-1 W1 from (A, 1) to (C, 1) justify left

# 3. then what they host — a typed product, placed along the host off a datum
door 3070 D1 in W1 at 4' past B      # the 3070 doortype states the size once, below

# 4. then the views — what to draw, at what scale, cut where (a query, never a
#    container). A view owns its file: each goes in its own views/ file.
view plan L1-plan of L1 scale 1/4" cut 4'-0" name "Level 1 Floor Plan"

Line anatomy — how a declaration reads

Every declaration is category → type → [id] → placement, with a slot dropped where the category doesn't carry it. One of the two leading name slots is always required, so the first name after the keyword is never ambiguous.

# element: the type leads and is REQUIRED; the id follows, optional
wall EXT-1 W1 from (A, 1) to (C, 1) justify left
wall STUD4 from 1 to 2 along A         # anonymous — id omitted, type still leads

# datum: no type — the REQUIRED id leads (a datum exists to be referenced)
level L1 elev 0' height 10'-0"
grid A vertical   at 0'

# door/window: the type leads here too (and is required); the id follows, optional
door 3070 D1 in W1 at 4' past B      # the 3070 doortype states the size once, below

# opening: the type-less cutter — a hole has no product identity, so its
# dimensions live on the line; id optional, like walls
opening O1 in W1 at 14' width 3' height 7' sill 4'

Lengths

Always carry units. Bare numbers are never lengths.

12'   8"   12'-6"   12'-6 1/2"   27.5'   4.5"   5/8"   3-5/8"   12ft   8in

Coordinates

(x, y), plan view: positive X is right (east), positive Y is up the page (north).

Each slot is a length literal or a position expression over datums:

(12', 28')     (A, 1)     (A + 14', 1)     (B - 4', 2)     (A + (B - A)/2, 1)

Expressions

Anywhere a position or distance goes, a static expression works: + - * /, parentheses, datum references.

grid A.5 vertical at A + (B - A)/2       # midpoint between grids A and B
grid D   vertical at C + 12'             # chained off C
door 3070 D4 in W1 at (2 - 1)/2          # centered: half the 1-to-2 span

Project & units

project "Sample Project"      # optional
units imperial                # optional; imperial is the v0 default

Levels

A level declares its story one way: height <len>, or top <level> [± offset] — the height is then derived (the named level's elevation minus this one's). Story height is a fact about the building, stated once.

level L1 elev 0' height 10'-0"
level L2 elev 10'-0" top L3        # story derived: L3's elev minus L2's
level L3 elev 21'-0" height 9'-0"
level L1                            # this FILE's level — the default base for its elements

Grids

grid A vertical   at 0'
grid 3 horizontal at 28'-0"
grid B.5 vertical at B + 12'-6"

Wall types

walltype STUD4 width 4.5" note "3-5/8 stud + gyp ea side"
walltype EXT-1 layers [brick 4", air 1", core: stud 6", gyp 5/8"]

Door & window types

The category requires; the type binds. Every door/window type must satisfy width and height — by fixing a value (stated once, every placement follows) or delegating it (the word instance: each placement supplies it).

doortype 3070 width 3'-0" height 7'-0"                # fixed: a product fact, stated once
doortype STOREFRONT width instance height 8'-0"       # width delegated to each placement
windowtype W-5040 width 5'-0" height 4'-0"
doortype HM-A:                                        # block form: one parameter per indented line
  width 3'-0"
  height 7'-0"
  u-fire-rating 90                                    # minutes
  u-hardware instance text required

Walls

A wall's line is stated one of two ways — by its endpoints, or by a carrier it rides plus two extents that cut it. Both are first-class; pick whichever states the facts you already know.

Carrier + extents is not a partition trick — an exterior wall running along a grid is the case it fits best. It is how a line-based element is placed — walls and roomlines today, every line element to come.

# by endpoints
wall EXT-1 W1 from (A, 1) to (C, 1) justify left

# by carrier + extents — the carrier fixes the line, two transverse datums cut it
wall EXT-1 W2 from 1 to 2 along A justify left      # ALONG grid A — no offset, no side
wall STUD4 W7 from 1 to 2 at 4' right of W2      # 4' clear of W2's core face
wall STUD4 from 1 to W5.end at 9' left of B      # grid carrier; a wall's line datum cuts too

# vertical facts — base and top name levels; one wall may span stories
wall EXT-1 W9 from (A, 3) to (B, 3) base L1 top L3   # a shaft wall, L1 up to L3

Element datums (anchors & accessors)

Every element projects datums you can reference — <id>.<datum>, kind-qualified when ambiguous (wall.W1.end).

wall EXT-1 W4 from W1.end to (C, 2)    # chain: W4 follows W1
wall STUD4 W5 from (A + 12', 1) to (A + 12', 2) top W1.top
door 3070 D5 in W1 at 6' past D1.jamb   # measured from D1's jamb, not its center

Doors

door 3070 D1 in W1 at 4' past B      # the 3070 doortype states the size once, below
door 3070 D2 in W5 at 2' past 1 swing left
door 3070 D3 in W5 at 3' short of 2
door STOREFRONT D6 in W1 at 20' width 6'-8"   # STOREFRONT delegates width; the placement supplies it

Windows

window W-5040 WIN1 in W2 at 6'-0" sill 3'-0"
window W-5040 WIN2 in W2 at 4' past WIN1   # 4' clear of WIN1's jamb

Openings

opening O1 in W1 at 14' width 3' height 7' sill 4'

Rooms

room 210 name "OFFICE" at (7', 14')   # number (the id — any text: 210, 210A, A-101), name, point; boundary and area are derived
roomline from (C, 2) to (C, 3)             # closes a boundary where no wall does — an open-plan split
roomline RL1 from W1 to 2 at 12' right of A    # positions like a wall: carrier + extents, or a point pair

Views

A view is a declared query over the model — never a container of elements. Every fact a view has is authorable on the view line itself; a viewtype is an OPTIONAL template — a named bundle of defaults — and a written fact on the view is a visible override. A view owns its file: each view statement lives in its own views/ file (its page), with only project, units, and viewtype declarations allowed beside it.

# untyped: kind → id → the level it is "of" → [crop] → [facts] → [name]
view plan L1-plan of L1 scale 1/4" cut 4'-0" name "Level 1 Floor Plan"
view plan L1-min of L1                     # every fact optional — scale defaults to 1/8", cut to 4'-0"
# or through a template: shared defaults, per-fact; the instance's own words win
viewtype plan A-PLAN scale 1/4" cut 4'-0" depth -10'-0"   # the view range: looks one story down
view A-PLAN L1-key of L1 scale 1/8"        # 1/8" here overrides the template's 1/4"
# the crop: lower-left corner to upper-right — what the view shows; corners are ordinary coordinates
view A-PLAN L1-kitchen of L1 from (A - 5', 1 - 5') to (C + 5', 2 + 5') name "Kitchen — Enlarged Plan"
# a section: "at" the cut plane, "to" the far clip — stations on one axis; the gaze is their sign
view section S1 at 1 + 4' to 3 name "Building Section"
view elevation E1 at 1 - 10' to 3 top L3 + 2' bottom L1   # trims anchor to LEVELS
view section S2 horizontal at 10' to 40'   # no grid named — the axis word says which way the plane runs

Reserved keywords

Every major building category is reserved for future versions — using one is an error (not silently ignored). Highlights:

component  import  export  const  refplane  detail  sheet  schedule
floor  roof  ceiling  stair  ramp  railing  column  beam  shaft
curtainwall  duct  pipe  conduit  furniture  site  topo  zone  space
floortype  rooftype  materialtype  hatchtype  layertype  …