room-interior-dimensions
by Cihad Paksoy
architecturefloor-plandimensionsroomsannotation
Description
Dimensions the clear interior width and length of every room in a floor plan, placed INSIDE each room and ALIGNED band by band. Measures from the real wall faces rather than a room's bounding box, so L/T-shaped rooms don't get a dimension ending in the neighbouring room, and dimension lines don't run across room name text. Use it for "dimension the rooms", "add interior dimensions", "measure each room's width and length".
Example prompts
- /room-interior-dimensions Dimension the interior width and length of every room in the region I marked.
- /room-interior-dimensions Add interior dimensions to this apartment, put them on the DIM layer.
- /room-interior-dimensions Measure each room and put the dimensions inside the rooms, aligned per row.
- /room-interior-dimensions Dimension the rooms in this plan and include the wall thicknesses between them.
Full content
---
name: room-interior-dimensions
description: Dimensions the clear interior width and length of every room in a floor plan, placed INSIDE each room and ALIGNED band by band. Measures from the real wall faces rather than a room's bounding box, so L/T-shaped rooms don't get a dimension ending in the neighbouring room, and dimension lines don't run across room name text. Use it for "dimension the rooms", "add interior dimensions", "measure each room's width and length".
tags: [architecture, floor-plan, dimensions, rooms, annotation]
---
When dimensioning a floor plan the goal is not just a correct number but a **readable sheet**:
dimensions that line up with each other, that don't cross the room labels, and whose ends stop
inside their own room. This procedure produces that.
## Scope: where is "the region I marked"?
When the user points at an area, do NOT invent the scope: a region **attached** to the message
(its label carries the bbox) wins if present; otherwise the user may have marked it in AutoCAD
with **CIZGENREGION** → call `get_marked_region` and use the returned bbox. If neither exists,
tell them to run CIZGENREGION; never make them re-pick something they already pointed at.
## Core principle: the dimension is BOUNDED by the find_spaces bbox
Walls in a plan are usually **double-line**: one wall is two parallel lines, and the room's face
is the **inner** one. Measure the outer line and you have measured the wall thickness as well —
the number grows and the dimension spills outside the room. This is the single most common
mistake.
**The `bbox` that `find_spaces` returns already sits ON the inner faces.** Polygonizing the
closed face takes the wall's inner line as the boundary. Therefore:
> **Do not go hunting for faces among the wall lines. The room's clear size is already in the
> `bbox`.**
**Fill test** — for every room:
```
fill = area / ((bbox[2]-bbox[0]) * (bbox[3]-bbox[1]))
```
- `fill ≥ 0.97` → the room is rectangular. Clear width = `bbox[2]-bbox[0]`, clear length =
`bbox[3]-bbox[1]`. **Do nothing else** — no wall-line reading, no face hunting. Done.
- `fill < 0.97` → the room is NOT rectangular (L/T/notched). The bbox's outer size is still
correct, but not every corner is a real face; measuring the bbox corner to corner puts the end
of the dimension in the middle of the neighbouring room. Apply step 4 for these.
**Unbreakable rule (both cases):** the measured value can **NEVER** exceed the room's bbox
extent on that axis. If `width > bbox width` or `length > bbox height`, you picked the wrong
face (you crossed to the outside of the wall) — don't draw it, recompute.
Compute the fill for every room and note the low ones — those are the rooms that need care.
## Procedure
### 1. Find the rooms
Call `find_spaces`; pass **every** layer that forms the walls in `boundary_layers` (walls +
columns + any room-dividing lines). **Read and act on** the feedback in the envelope:
- `missing_boundary_layers` → add those layers to `boundary_layers`, call again.
- `merged_labels` → two rooms collapsed into one face; adjust `max_bridge_gap` and retry. If it
won't separate, declare them as ONE room — never split on an assumption.
- Iterate until `multi_label_faces = 0` (or until you stop making progress).
**Put only structural/dividing layers in `boundary_layers`** — walls, columns, shear walls, room
dividers. Never make a furniture/equipment/fixture layer a boundary: the equipment outline then
polygonizes as a "room" and you dimension the equipment instead of the space. If
`missing_boundary_layers` suggests an equipment layer, don't add it; handle that label in step 1b.
**Dimension only NAMED faces.** Small unnamed faces are door/wall-recess artifacts. Declare how
many you skipped.
### 1b. Separate shaft-type faces
Lift shafts, light wells, flues and service shafts are **not rooms**, and they contain equipment
geometry (a lift car, a duct). `find_spaces` may hand you the equipment outline as a face —
dimension that and you have measured the car, not the shaft's clear structural opening.
Treat a face as suspect if its name denotes such a volume (lift / shaft / flue / light well /
duct), **or** it is nested inside another face, **or** its boundary carries no line from a wall
layer.
In such a volume what you dimension is the **clear structural opening**: between the faces of
the enclosing WALLS. Measure the inner faces of the walls around the volume (`cad.query` for the
wall lines around it), never the equipment outline as a dimension endpoint. Declare which volumes
you measured this way.
### 2. Determine the dimension layer
Use whatever the user named. If they didn't, take the layer of the EXISTING dimensions in the
drawing (count Dimension entities with `aggregate` or `query`, take the busiest layer) — never
invent a layer name. If there are no dimensions at all, ask the user.
### 3. Build the bands — alignment is won here
Throwing two dimensions at every room in isolation is technically correct but the sheet looks
scattered. The architect's practice: **dimensions of rooms in the same row sit on the same line.**
- **Horizontal dimensions (width):** rooms whose Y ranges overlap form a **band**. Pick ONE
dimension-line Y for the band; that Y must fall inside every room in the band.
- **Vertical dimensions (length):** rooms whose X ranges overlap form a **column**; pick ONE X.
A room can belong to several bands; every room must get at least one horizontal and one vertical
dimension.
**When picking the band Y** (same for a column's X):
1. Generate candidates from the band's common interior range.
2. Score each candidate: the one that misses the TEXT boxes in the band's rooms wins (read the
texts with `read_texts` over the room bbox; use the `height` field, don't invent a point size
— a text's band is roughly `[pos_y - height, pos_y + 2×height]`).
3. On a tie, pick the one further from door blocks (keep the area in front of a door clear).
**Priority rule:** alignment is worth more than a clash in a single room. If no clean Y exists
for the whole band, ACCEPT the text crossing in one or two long-named rooms; do not break the
band and give every room its own height. (Say so in your answer.)
### 4. ONLY for non-rectangular rooms: clip the line inside the bbox
Rooms with `fill ≥ 0.97` skip this step — your measurement already came from the bbox, go
straight to step 5.
In a room with `fill < 0.97`, part of the room is narrower at your band Y. The job is not to
find a new face but to **pull the bbox edge inward**:
1. Collect the wall-layer lines (`cad.query(bbox, {layer, handles:true})` → `cad.entities(handles)`).
Restrict the search to the room's **own bbox**.
2. For a horizontal dimension: take the **vertical** lines (|x₂−x₁| ≈ 0) whose Y range contains
the band Y. Only those falling strictly within `bbox[0] < x < bbox[2]` are useful — lines on
or outside the bbox edge are DISCARDED (the outside ones are the wall's outer line; measure
them and the thickness enters the dimension).
3. Take an interior reference point on that line. Left bound = the X of such a line to its left
if one exists, otherwise `bbox[0]`. Right bound = the one to its right, otherwise `bbox[2]`.
4. A vertical dimension is the same thing rotated 90° (horizontal lines, `bbox[1] < y < bbox[3]`).
The result is always equal to or SMALLER than the bbox. If it came out larger, you skipped the
filter in point 2.
### 5. Add the wall thicknesses too
Once the dimensions are aligned, the wall between two neighbouring rooms is dimensioned on the
same line: from room A's right face to room B's left face. This is what completes the chain look;
don't skip it. (The values typically repeat — 10/20/25 and so on; that is normal, the architect
draws it the same way.)
### 6. CHECK every dimension before drawing
Before you draw, check these five for every planned dimension. If even one fails, fix that
dimension — don't draw first and look later:
1. **Does it exceed the bbox?** `width ≤ bbox width` and `length ≤ bbox height` are MANDATORY. If
it exceeds, you picked the wall's outer line and the thickness is in the dimension — don't
draw, recompute. In a rectangular room (`fill ≥ 0.97`) the value must **equal** the bbox;
smaller is wrong too.
2. **Do the ends stay in their own room?** Both endpoints must sit on the room's real boundary. A
dimension whose end spills into the neighbouring room is wrong (in a non-rectangular room this
is the typical symptom of skipping step 4).
3. **Does the dimension line cross a text?** If so, shift the line (the priority rule in step 3
still applies).
4. **Does the dimension text fit?** If the measured length is under about 3× the text height the
text won't fit on the line. In such a room move the dimension OUTSIDE the room, or skip it and
DECLARE it (very narrow faces such as a wardrobe recess).
5. **Are the band's dimensions on the same coordinate?**
### 7. Draw
Produce all dimensions with a **single `run_script`** through `cad.draw` (`dimension` type:
`{kind:'linear', p1, p2, line, rotation}`; `rotation: 0` for horizontal, `Math.PI/2` for
vertical). Don't call `draw` one at a time.
### 8. Check ROOM BY ROOM — do not skip this step
Looking at one overall render and calling it clean is NOT enough: with every room squeezed into
one small image, overlapping text is invisible, and `draw_lint` does not check layout (it checks
geometry). So walk the rooms **one at a time**. For each room, in order:
**8a. Find clashes with NUMBERS first — not by eye.** A text's real box is the `BoundingBox`
field in the `get_objects_in_region` output (true glyph extents for DBText/MText; don't estimate,
don't derive a width from the character count). Intersect every text box in that room against:
- the dimension line's band,
- the place where the dimension's own text will sit (the middle of the dimension line),
- the other dimensions' lines.
If they intersect, shift the dimension line within the room and REPEAT the test. Shift away from
the text, toward the emptier side of the room (text near the left → dimension to the right; text
low → move up). If no clean coordinate remains for the whole band, the priority rule from step 3
applies: keep the alignment, accept the clash, and DECLARE it.
**8b. Is a wall dimension missing?** Between this room and its right/upper neighbour there should
be a wall-thickness dimension on the same band (step 5). If it isn't there, add it now. This is
the most-skipped step — ask it separately for every room.
**8c. Then render that room ON ITS OWN.** Give `render_and_verify` the room's bbox (with a little
margin) — not the whole region. At that zoom, overlaps, a line cutting through the room name and
an end spilling into the neighbour all become visible. Fix what you see, re-render that room, and
do NOT move to the next room until it is clean.
Accumulate one row per room for the table in step 9.
### 9. Submit for approval
Once every room is clean: `create_preview` → user approval → `commit_changes`. Do not present a
preview before the check is finished.
## Declare in your answer
Give a **one row per room table** from the rows you accumulated in step 8. Leave NO row out — if
you didn't check a room, saying so beats skipping it silently:
| room | width × length | bbox | clash | neighbour wall dim |
|---|---|---|---|---|
| BEDROOM | 280 × 430 | 280 × 430 ✓ | none | yes (10) |
| KITCHEN | 280 × 536 | 440 × 536 (L) | none | yes (25) |
- If `width × length` is LARGER than the bbox, the wall thickness got into the dimension — the
user catches the error in this column without opening the drawing.
- Also, briefly: how many unnamed faces were skipped; which volumes were measured wall-to-wall as
shafts; which rooms had their dimension moved outside or skipped for being too narrow; which
text clashes were accepted for the sake of alignment.
## Known limitation
`auto_dimension` places its chain **outside** the selection (`edge` + `offset`); it cannot put a
chain line running THROUGH the plan. That is why this procedure builds the alignment by hand via
band selection. If the user wants dimensions around the outside of the plan, `auto_dimension` is
the right tool there.
## Example prompts
- /room-interior-dimensions Dimension the interior width and length of every room in the region I marked.
- /room-interior-dimensions Add interior dimensions to this apartment, put them on the DIM layer.
- /room-interior-dimensions Measure each room and put the dimensions inside the rooms, aligned per row.
- /room-interior-dimensions Dimension the rooms in this plan and include the wall thicknesses between them.