bimtex
The format

Openings

One hole in one wall, and every drawing reads the same hole.

An opening is three answers: which host, what kind, and where along that host's run. The host is usually a wall; a pitched roof can also carry one — see a roof is also a host below.

{"t":"opening","id":"front","on":"south","kind":"door","width":0.95,"room":"living"}
{"t":"opening","id":"window","on":"north","kind":"window","at":"third","width":1.8,"sill":0.9,"head":2.3}

You never say where the hole is in the building — only where it is along its host. The wall already knows where it runs, so the opening does not need to, and the two can never disagree about it.

Placing it

Leave at off and the opening centres itself. That is the right answer more often than people expect, and it is the one that survives the wall changing length.

When centre is wrong, say where in words: start, end, third, two-thirds, center. These state intent, so they still mean what you meant after the building moves.

A numeric at is where the hole starts, not where it is centred. {"at":4,"width":2} occupies 4.0 to 6.0 m along the run — not 3.0 to 5.0.

This is the one field in the format that will do something perfectly reasonable and not what you meant: no error, no warning, a door a metre from where you put it. Reach for a named position unless an exact axis coordinate is genuinely the point.

Which rooms it serves

An exterior door names one room: "room":"living".

An interior opening names two, in order: "between":["kitchen","living"] — from the first, swinging into the second. That ordered pair is doing two jobs at once. It builds the connectivity graph the reachability and egress checks walk, and it decides which way the leaf swings on the plan. Get the order backwards and the drawing shows a door opening into the room you meant to leave.

What the compiler does with it

It cuts the span [sill, head] out of a one-dimensional wall run and keeps what is left: the pieces before, below, above and after the hole.

Every drawing then consumes those same pieces. Nothing re-derives the window per view, which is why it cannot be a gap in plan and solid wall in section.

A roof is also a host

on may name a pitched roof — gable, hip or shed — and the opening becomes a skylight:

{"t":"opening","id":"sky","on":"roof-main","kind":"window","width":1.2,"at":"third","up":1.4}

kind must be window; a door on a roof is a schema error, not a hatch. at places it along the ridge axis exactly the way it places along a wall, and up is the new axis a slope adds — metres up the plane from the eave to the sill edge, centred between eave and ridge when omitted. It draws where a drafter would draw it: on the roof plan, dashed overhead on the floor plan, as a gap with its glazing bar where a section cuts it, and on the roof face in the elevation that sees the slope.

A dormer is more than a hole, so it is its own entity rather than an opening variant: {"t":"dormer","on":"roof-main","at":6,"width":2.2} compiles cheek walls, a perpendicular gable mini-roof and one face window from the machinery the rest of the model already uses. A dormer or skylight that leaves its host plane — past the ridge, past the eave, off the rectangle — is a blocking error, and flat roofs and formdef roofs host neither.

What bites

kind decides which operation values are legal. A door is swing, sliding, overhead or double-acting. fixed belongs to a window. A cased opening with no leaf at all is not a door with the leaf removed — it is kind:"opening".

An opening wider than its wall is caught, and it is blocking. So is one hosted on a wall that does not exist. Both name the host in the message.

between is probed against the geometry. If the wall at that point does not actually stand between the two rooms you named, the check fails rather than trusting the label. It is not being difficult — a connectivity graph built from wishes is worth nothing.

Where to go next