feat(arch): the wall engine - L/T/X junctions, opening gaps and material poche

engineering/arch/walls.py turns a wall network - axis polylines with a
thickness, a justification and a material - into clean outlines. One rule
resolves every junction: at each node the segment ends are sorted by angle and
the left face of each meets the right face of the next one counter-clockwise.
That mitres an L, stops a T's stem on the crossing wall's near face (cut there,
while the far face stays one line) and cuts all four faces of an X. A T is an
axis end lying inside another wall: it is carried onto the crossing axis and
the crossing segment is split. The hub a three- or four-way node leaves is a
region of its own, so the poche has no hole.

Openings remove their width from both faces and are closed by jambs; a free
end is closed by a jamb. Each solid piece is one CCW region and one HatchArea
in the wall's material. The two face lines across each opening are kept as
thresholds - never drawn - so room_segments hands the face finder closed rooms
instead of rooms merged through their doorways.

Refused by name before any geometry: a duplicate id, an unknown material or
justification, a zero-length segment, a junction below 5 degrees, a corner
inside another wall off its axis, an end whose axis meets the other wall past
its end, a segment lying wholly inside the wall it joins, and one too short
for its junctions (engineering.measure.is_self_intersecting on its outline).
An opening that runs into a junction is returned in omitted, never dropped.

Every corner in the tests is hand-computed: two 200 mm walls at the origin
meet at (100, 100) inside and (-100, -100) outside.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
Umut CAN
2026-09-23 22:33:31 +03:00
co-authored by Claude Opus 5.5
parent d5c5e90406
commit dae48d9037
3 changed files with 996 additions and 0 deletions
+16
View File
@@ -30,6 +30,15 @@ from .model import (
wall_from_dict,
wall_length,
)
from .walls import (
MIN_JOIN_ANGLE_DEG,
WallGeometry,
face_offsets,
room_segments,
wall_geometry,
wall_geometry_by_wall,
wall_segments,
)
__all__ = [
"APP_ID",
@@ -39,20 +48,24 @@ __all__ = [
"ARCH_ROLE_LAYER",
"KINDS",
"LANGS",
"MIN_JOIN_ANGLE_DEG",
"Opening",
"Room",
"Stair",
"WALL_HATCH_TABLE",
"WALL_MATERIALS",
"Wall",
"WallGeometry",
"axis_segments",
"decode",
"encode",
"face_offsets",
"fmt_area_m2",
"fmt_number",
"from_payload",
"opening_from_dict",
"room_from_dict",
"room_segments",
"segment_of",
"stair_from_dict",
"tag_prefix",
@@ -61,6 +74,9 @@ __all__ = [
"validate_openings",
"vocab",
"wall_from_dict",
"wall_geometry",
"wall_geometry_by_wall",
"wall_hatch",
"wall_length",
"wall_segments",
]