One plan model — a wall network with hosted openings — and one engine that resolves L/T/X junctions, cuts openings, hatches cut walls by material and computes rooms as the faces the walls enclose, so areas are measured, never typed. A pure planar-face finder reads rooms from foreign plans on both engines (the live engine has no boundary_trace). Furniture/sanitary catalogue, grid, symbols, exterior dimension chains, schedules from the model. 12 tools (SECTION 25, pack arch), English by default with lang="tr", poché by material, three arch_* critique focuses, benchmark arch_roundtrip. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
12 KiB
v1.6 Track F — architectural 2D: the plan model
Date: 2026-09-23 · Status: design approved in conversation ·
Parent: 2026-09-15-v1.6-roadmap-design.md (track F) · Baseline: main
after tracks B + G (2bba18d, 229 tools, 4005 tests)
1. Summary
An architectural plan is not a set of lines: a door opens the wall it sits in, two walls that meet join into one clean corner, and a room's area is the floor the walls enclose — a number that must be measured, never typed. Drawing a plan from primitives gets all three wrong, silently: the door lies on top of an uncut wall, the corner overlaps, and the area in the label is whatever the model wrote.
This track applies the lesson of tracks B + G: one plan model and one
engine, not a zoo of element tools. The model is a network of walls (an
axis polyline, a thickness, a justification and a material) with hosted
openings (doors and windows placed by distance along their wall). The engine
turns it into clean wall outlines — L, T and X junctions resolved, openings cut,
cut faces hatched by material — and rooms are computed as the faces the wall
outlines enclose. Door, window and room schedules are read back from the model;
a separate reader finds rooms in a foreign plan made of plain lines. Both
run on both engines by construction: the planar-face finder is pure geometry
and does not need boundary_trace, which the live engine does not have.
Around it: a furniture and sanitary block catalogue (the standard-parts pattern of track B), the structural grid, the architectural symbols and exterior dimension chains.
Tool budget: +12 → 241. New pack arch (SECTION 25). Labels and schedules
speak English by default and Turkish with lang="tr" (the user's decision,
2026-09-23), including the decimal comma.
2. Scope
In: SECTION 25 Architecture (12 tools, §8), the arch ISO 13567 layer set,
three arch_* critique focuses, benchmark task arch_roundtrip, correctness
checks and a live COM smoke.
Out of 1.6 (recorded):
- AutoCAD Architecture / Revit objects (AEC walls, door styles), IFC, 3D walls, generated elevations and sections — the section mark is a symbol; no view is derived from a plan.
- Curved walls. A wall axis is a polyline of straight segments; an arc segment is refused by name rather than approximated.
- Annotative objects (recorded in track E): text heights are computed from the
plot scale instead (
scale="1:50"→ 2.5 mm × 50 = 125 drawing units). - Code compliance (fire escape widths, accessibility, national stair rules): the stair tool checks the Blondel relation and reports it; it does not claim a building code.
3. Architecture
engineering/arch/
__init__.py
layers.py the `arch` ISO 13567 layer set + role → layer map
materials.py wall materials → ISO 128-50 area hatch (brick, aac, concrete, …)
lang.py EN / TR vocabulary: schedule headers, designations, area format
model.py Wall, Opening, Stair, Room — dataclasses, validation, ACADMCP_ARCH payload
walls.py the wall engine: offset outlines, junction clean-up, opening cuts, poché
openings.py door and window geometry (leaf, swing, frame, glazing, sill)
stairs.py straight / L / U stair plan symbols, the Blondel check
faces.py the planar-face finder: segments → closed faces (DCEL), areas
rooms.py room labels (measured area), the foreign-plan reader
schedule.py door / window / room schedules from the model
catalogue.py furniture + sanitary blocks as typed primitive specs (block_define)
symbols.py grid bubbles, north arrow, section mark, level mark
dimension.py exterior dimension chains (openings / walls / overall)
draw.py the async layer — the only module that touches a backend
critique.py the three arch_* critique focuses
Everything but draw.py is pure: numbers in, primitive descriptions out,
using the typed primitives of engineering/mech/primitives.py (Line, Arc,
Poly, HatchArea, Text, DimIntent) with the arch role → layer map. No new
contract member: every tool composes the generic ones (entity_create_*,
dimension_*, block_define, entity_set_xdata, entity_list), so it works
on both engines as written, exactly like the mechanical tools.
The plan is written onto the drawing as ACADMCP_ARCH XDATA (the chunked codec
of track B, generalised to take an application id): a wall record on each wall
outline, an opening record on each opening, a room record on each label — so a
schedule is a read of the drawing, and a second arch_opening call months
later finds the wall it cuts.
4. The plan model
class Wall: # frozen dataclass
id: str
axis: tuple[Pt, ...] # 2+ points, straight segments, WCS
thickness: float # > 0
justification: "center" | "left" | "right" # side of the axis, looking along it
material: str = "brick" # → poché hatch
closed: bool = False
class Opening:
id: str
wall: str # the Wall id it is hosted in
kind: "door" | "window"
offset: float # along the wall axis, from its start, to the opening's near jamb
width: float
swing: "in" | "out" = "in" # doors
hand: "left" | "right" = "left"
sill: float | None = None # windows, informational (schedule)
height: float | None = None # schedule
tag: str | None = None # D1 / W1 (EN), K1 / P1 (TR) when omitted
class Stair:
id: str
start: Pt; direction_deg: float; width: float
risers: int; riser_height: float; going: float
kind: "straight" | "l" | "u" = "straight"; turn: "left" | "right" = "left"
class Room:
id: str; name: str; number: str | None; at: Pt # a point inside the face
area: float # measured, written by the tool
Validation before any write (the repository rule): finite positive numbers by
path (walls[2].thickness), an axis of ≥ 2 distinct points with no zero-length
segment, an opening whose offset + width lies inside its wall segment and does
not straddle a vertex, two openings on one wall that overlap refused with both
ids, an unknown material or language refused with the list.
5. The wall engine
- Outlines. Each segment is offset by the justification into two faces.
- Junctions. Where segments meet — within one wall or between walls — the faces are trimmed or extended to their intersections: an L corner mitres, a T junction stops the stem's faces on the crossing wall's face, an X junction cuts all four. A junction the engine cannot resolve (a segment ending inside another wall at a shallow angle below 5°) is refused by name, not overlapped.
- Openings. An opening removes its width from both faces of its wall and closes the two jambs; a door draws its leaf at 90° and the swing as a quarter arc on the swing side; a window draws the frame line on each face and two glazing lines between.
- Poché. Each closed cut-face region between openings and junctions is a
HatchAreawith the material's ISO 128-50 pattern;poche=Falsedraws the outlines only. - Honesty. Anything the engine cannot place is returned in
omitted: [{element, reason}]— never silently dropped.
6. Rooms
faces.py builds a planar graph (a doubly connected edge list) from line and
polyline segments: segments are split at every intersection, near-coincident
endpoints are merged within a tolerance (default 1 mm), and the bounded faces
are walked. A room is the face containing the label point; its area is the
face's polygon area and is written into the label and the room record.
arch_rooms_detect runs the same finder on a foreign plan — any lines and
polylines on the layers it is given (default: every layer whose name contains
WALL or DUVAR) — and returns faces above a minimum area with their areas,
centroids and a confidence (1.0 for a face enclosed only by our walls, 0.6
for plain lines), the way pid_graph does. It never modifies the drawing.
7. Schedules, symbols, catalogue, dimensions
arch_schedule(kind="doors"|"windows"|"rooms", at, lang)reads theACADMCP_ARCHrecords and draws a real TABLE (the parts-list code of track G): doors and windows by tag with width, height, swing/hand and wall; rooms by number with name and measured area, plus the total.arch_symbol(kind="north_arrow"|"section_mark"|"level"|"elevation_mark", …).arch_grid(x_axes, y_axes, labels)— axis lines on the grid layer with bubbles at both ends; numbers one way and letters the other (ISO 4157-style).arch_catalogue_list/arch_catalogue_insert— ~20 authored blocks in two families: furniture (bed single/double, wardrobe, sofa, armchair, dining table- chairs, desk, kitchen counter, fridge) and sanitary (WC, basin, shower tray, bathtub, kitchen sink). Sizes are nominal catalogue dimensions stated in the block's own data, not standards values, and the tool says so.
arch_dimension_chains(sides, offsets)— the three exterior chains an architectural plan carries: openings, wall segments / axes, overall.
8. Tool surface
| # | Tool | What it does |
|---|---|---|
| 1 | arch_wall |
add walls to the plan network and redraw the junctions they touch |
| 2 | arch_opening |
host a door or a window in a wall (kind=), cutting it |
| 3 | arch_stair |
a straight / L / U stair in plan, with the Blondel check |
| 4 | arch_room |
label a room with its measured area |
| 5 | arch_rooms_detect |
read rooms and areas from any plan (never modifies) |
| 6 | arch_schedule |
door / window / room schedule table |
| 7 | arch_grid |
structural grid with bubbles |
| 8 | arch_symbol |
north arrow, section mark, level, elevation mark |
| 9 | arch_catalogue_list |
search the furniture / sanitary catalogue |
| 10 | arch_catalogue_insert |
insert a catalogue block |
| 11 | arch_dimension_chains |
the exterior dimension chains |
| 12 | arch_plan_from_spec |
walls + openings + stairs + rooms + chains + schedules in one transaction |
Pack arch holds all twelve. Lean gains arch_wall, arch_opening,
arch_room, arch_plan_from_spec (→ 64). drawing_apply_iso_layers("arch")
applies the new layer set. Every tool has an alias record with the AutoCAD
commands a drafter would reach for (WALL, DOOR, WINDOW, STAIR, BOUNDARY, AREA)
and shop synonyms in both languages (duvar, kapı, pencere, merdiven, oda, alan,
kat planı, vaziyet).
9. Critique focuses
arch_room_unlabelled— a closed face above the minimum area with no room label inside it.arch_wall_gap— two wall ends within 50 mm of each other that are not joined: the room leaks and its area is wrong.arch_opening_clash— two openings overlapping on one wall, or an opening that runs past its wall segment.
All three return nothing on a drawing with no ACADMCP_ARCH records.
10. Evidence
- Benchmark
arch_roundtrip(roadmap evidence for F): a two-room plan with an entrance door, an interior door, two windows and a stair drawn byarch_plan_from_spec;arch_rooms_detectreads it back and each room's area agrees with the model's within 0.1 %; the door and window schedules list the drawn tags;drawing_critique(focus=None)returns 0 issues anddrawing_finalizescores ≥ 90. Matrix v6 (19 tasks). - Correctness checks:
arch_junction_l_t_x(outline vertices of the three junction kinds against hand-computed coordinates),arch_room_area_net(a 4 × 5 m room between 200 mm walls measures 20.00 m², not the axis area),arch_opening_cuts_wall(the wall faces are interrupted across the opening),arch_rooms_detect_foreign(a plan of plain lines yields the same areas). - Live smoke
scripts/smoke_arch_com.pyon AutoCAD 2026.