A T recorded its cut on the crossing segment as a distance from that
segment's start; when the start was carried onto a third wall later in the
same pass the cut went stale, so [c,b,a] drew a free-end jamb inside wall a,
an uncut face and a double-hatched overlap where [c,a,b] was right. Cuts are
now stored as points and measured against the final axis.
A near-straight join whose faces are offset (a thickness or justification
change, or a rounding kink between such walls) mitred at offset/sin(kink):
faces 1e8 mm long and a clockwise region for a 0.01 mm rounding. A
straight-through pair whose mitre lies more than _MITRE_REACH (2) thicknesses
from the node now steps on the line bisecting the join - both sides of a
two-end node together, so the pieces tile without a sliver - and every piece
and hub ring is refused unless counter-clockwise, not only when it crosses
itself.
Pure geometry: no ActiveX member is touched, so no live probe applies.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
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
Track F starts here; the three group branches are cut from this commit.
- engineering/arch/model.py: Wall, Opening, Stair and Room as frozen
dataclasses, and every refusal of spec 4 before anything is drawn, by path
(walls[2].thickness): finite positive numbers, an axis of at least two
distinct points with no zero-length segment, an arc segment refused by name
rather than approximated, an opening on an unknown wall, past its wall,
across an axis vertex or overlapping another (both ids named), an unknown
material, and a typed room area - areas are measured, never typed. The
plan travels as ACADMCP_ARCH XDATA; an opening's door/window kind and a
stair's straight/l/u kind ride beside the payload kind as opening_kind /
stair_kind, because one key cannot hold both.
- engineering/arch/layers.py: the arch set - thirteen ISO 13567-style layers
with ISO 128 lineweights only (0.05 mm on the construction scratch layer,
as every set in this repository) - and ARCH_ROLE_LAYER, the one place a
role becomes a layer. Registered in engineering/layers.py last, which is
what keeps the engineering.layers <-> engineering.arch import cycle closed.
- engineering/arch/materials.py: eight wall materials, one pattern each,
every row citing its source: ANSI31 for brick (ISO 128-50 general hatching,
acadiso.pat's own "Iron, Brick, Stone masonry"), AAC as ANSI31 at twice the
spacing (a repository convention, and it says so), AR-CONC / AR-SAND as
AutoCAD patterns, and the JIS A 0150 RC / stone / wood symbols. Reinforced
concrete cannot be ANSI31 and AR-CONC in one HATCH, so it takes the one
symbol that names it. Periods on a 1:50 sheet are recomputed from ezdxf's
own pattern definitions by the tests. normalise_material is the one reading
of a material name, shared by the model and wall_hatch, so the model never
refuses "Reinforced Concrete" that the hatch table resolves.
- engineering/arch/lang.py: EN / TR vocabulary, D/W and K/P tag prefixes, and
the decimal comma for lang="tr" (24,50 m2).
- draw_prims(role_layer=) and the XDATA codec's app_id= / kinds=: both
generalised with the mechanical defaults, and the mechanical suite passes
unchanged against them - that is the regression gate.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd