diff --git a/docs/superpowers/specs/2026-09-23-v1.6-arch-design.md b/docs/superpowers/specs/2026-09-23-v1.6-arch-design.md new file mode 100644 index 0000000..7d1af99 --- /dev/null +++ b/docs/superpowers/specs/2026-09-23-v1.6-arch-design.md @@ -0,0 +1,226 @@ +# 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 + +```python +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 + `HatchArea` with the material's ISO 128-50 pattern; `poche=False` draws 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 the + `ACADMCP_ARCH` records 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 by + `arch_plan_from_spec`; `arch_rooms_detect` reads 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 and + `drawing_finalize` scores ≥ 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.py` on AutoCAD 2026.