Commit Graph
10 Commits
Author SHA1 Message Date
Umut CANandClaude Opus 5.5 4c5c8227ef merge: track F group C
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-23 23:05:27 +03:00
Umut CANandClaude Opus 5.5 18db0b46c0 merge: track F group R
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-23 23:03:13 +03:00
Umut CANandClaude Opus 5.5 cd1ae3529c feat(arch): SECTION 25 opens - walls, openings, stairs and exterior chains as tools
engineering/arch/draw.py is the only module of the track that touches a
backend. It draws through draw_prims(role_layer=ARCH_ROLE_LAYER) and the
generic contract members, so it runs on both engines as written, and it writes
the plan onto the drawing: each element's ACADMCP_ARCH record on one of its own
entities (a wall's first face line, a door's leaf, a window's frame, a stair's
first riser) and, beside it under ACADMCP_ARCH_H, the handles that element
owns. read_plan reads the records back and never modifies the drawing.

arch_wall computes the whole network before and after the new walls and
redraws only the drawn walls whose outline changes - erasing exactly the
entities they own - and says which under redrawn. arch_opening hosts a door or
a window in a drawn wall found by id and redraws that wall cut; its tag
defaults to the next D1/W1 (K1/P1 in Turkish). A new wall that would leave a
drawn opening uncuttable, and an opening that runs into a junction, are refused
before anything is erased. arch_stair reports the Blondel value; it does not
enforce it.

exterior_chains gives each side up to three rows, nearest first: every jamb in
that facade, both faces of every wall meeting it, and the overall - from the
wall engine's own faces. A row that would only restate a farther one is not
emitted (ISO 129-1: each measurement once). The 10 mm / 8 mm row spacing is a
declared drafting default times the plot scale, not a standard value.

The arch tag is ranked after mech so the four tools file under a new
architecture group; alias records carry WALLADD/MLINE, DOORADD/WINDOWADD and
STAIRADD with English and Turkish shop words. Inventory regenerated; README
snapshot 233 tools (228 advertised) in 26 groups; registry snapshot
architecture: 4.

No new ActiveX member is called. Measured on AutoCAD 2026 in a scratch
document through ComBackend: shell + teed partition, D1 + W1, a straight 17-riser stair
and the bottom chains gave the same 20 wall LINEs (identical endpoints),
9 poche HATCHes, 1+1 door, 4 window lines, 11 dimensions and a read_plan
round trip as the headless engine.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-23 23:00:05 +03:00
Umut CANandClaude Opus 5.5 9168d61397 feat(task-6): door, window and room schedules read from the drawing
engineering/arch/schedule.py turns the plan records into schedule rows: doors
by tag with width, height, swing, hand and wall; windows by tag with width,
height, sill and wall; rooms by number with name and the measured area the
label carries, plus a total. Tags compare as numbers (D2 before D10). An
opening without a tag gets the next free D1 / W1 - K1 / P1 in Turkish - in
wall order; an explicit tag is never renumbered and a generated one never
repeats it. A value the model does not carry is an empty cell, never a default,
and Turkish output writes decimal commas.

draw_schedule reads the ACADMCP_ARCH records off the drawing through the one
reader rooms.py already has and draws a real TABLE the way the ISO 7573 parts
list does - top-left insertion, the table growing down, the parts list's 7 mm
rows and 2.5 mm text at the plot scale, the engine's representation reported.
Nothing to schedule, an unknown kind or language and more rows than one TABLE
takes are refused before anything is drawn.

arch_schedule joins arch_room and arch_rooms_detect under SECTION 25's rooms
marker (banner 3 tools), shares the TABLE command with entity_create_table and
bom_table, and the counts follow: 232 tools in 25 groups.

Live fix in com_backend.entity_create_table. MEASURED on AutoCAD 2026: IAcadTable
has no TextHeight property. pywin32 raised AttributeError on
`table.TextHeight = h`, the old code swallowed it, and every live table kept
the Standard style's heights (title 6.0, header and data 4.5), which is 0.1 mm
on paper at 1:50. The member that works is SetCellTextHeight(row, col, h),
probed on a scratch document: every cell read back 125.0 via
GetCellTextHeight. The row heights stayed 350.0 and GetBoundingBox matched the
reported bbox (10000,3250)-(15750,5000). Then draw_schedule ran end to end
through ComBackend on a scratch document: a native TABLE on A-SYMB-T-N for
doors (5 rows) and rooms/tr (6 rows, total 45,00 m²), and the document was
closed afterwards. The new fake in tests/test_table_leader.py has only the
measured members.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-23 22:56:05 +03:00
Umut CANandClaude Opus 5.5 b68fe7ef66 feat(arch): door and window symbols, straight/L/U stairs and the Blondel check
openings.py draws what stands in the gap the wall engine cuts, from the same
numbers - the wall axis, its face offsets and model.segment_of - so the symbol
cannot drift off its gap. A door is drawn open at 90 degrees: its leaf as long
as the opening is wide, square to the wall from the hinge on the swing-side
face, and a quarter arc from the closed position to the open one. swing=in is
the left of the axis (the inside of a CCW perimeter); hand names the hinge jamb
seen from the swing side facing the wall. All four swing/hand pairs are tested
against hand-computed hinges and arc angles. A window is a frame line on each
face and two glazing lines at the thirds of the thickness.

stairs.py draws every riser once as a line across its flight, the outline, a
walking line up the middle with an open arrow at the top, and the language's
'up' word on the first tread at 2.5 mm times the plot scale. An L or a U turns
on a square landing; its first flight carries (risers + 1) // 2 risers. The
riser count and every dimension are refused by name before any geometry.

blondel() reports 2R + G against 600-650 mm - the step-length rule attributed
to Francois Blondel (1675), reported as a design rule and never as a building
code: no national stair regulation is checked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-23 22:51:41 +03:00
Umut CANandClaude Opus 5.5 f5f11178a9 feat(task-8): SECTION 25 grid and symbols - arch_grid, arch_symbol
engineering/arch/symbols.py is pure: grid_prims() draws each structural axis on
the grid role - its CENTER linetype comes from the A-GRID-E-N row, no primitive
sets one - with a 5 mm (paper) bubble and its label touching both ends, on the
symbol role so the circle does not print dashed. Numbers run along x and
letters along y (A..Z, AA, ... none skipped) unless labels names them;
positions are sorted, duplicates and label-count mismatches refused by path.

symbol_prims() draws the four plan symbols: the north arrow (a circle, an
arrow turned CCW from +Y, the vocab(lang) north letter upright beyond the tip),
the section mark (the cut line, a labelled bubble past each end and a viewing
arrow at each end, p1/p2 relative to the origin), the level mark (the triangle,
its line and the value formatted +3.00 / ±0.00, with a decimal comma in
Turkish) and the elevation mark (a circle whose pointers are the tangents from
r*sqrt(2) touching it at +/-45 degrees). An unknown kind is refused with the
list, a parameter the kind does not take with the ones it does. Every size is
a declared paper-mm drafting choice times the plot scale, not a standards value.

arch_grid and arch_symbol draw through draw_prims with ARCH_ROLE_LAYER; alias
records (COLUMNGRIDADD, English and Turkish synonyms), a regenerated inventory,
the README snapshot at 233 tools and the branch-local group snapshot
(entity_creation 21) complete the registration.

Beyond the plan: grid_axes refuses a labels value that is not a dict and a
labels.x / labels.y that is not a list (a string was split into one label per
character, a number raised a bare TypeError). No new ActiveX member: a live run
on AutoCAD 2026 (scratch document, closed after) drew the grid and all four
symbols through draw_prims - A-GRID-E-N created with CENTER, bubbles, texts and
symbols on A-SYMB-T-N, the Turkish north letter K and the level "+3,00".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-23 22:42:42 +03:00
Umut CANandClaude Opus 5.5 afd42356b5 feat(task-7): SECTION 25 catalogue - arch_catalogue_list, arch_catalogue_insert
engineering/arch/catalogue.py is the standard-parts pattern of track B for an
architectural plan: twenty authored plan symbols in two families - thirteen
furniture items (single and double bed, wardrobe, three-seat sofa, armchair,
chair, dining table with four chairs, desk, kitchen counter run, fridge,
bookshelf, nightstand, coffee table) and seven sanitary items (WC, bidet, wall
basin, shower tray, bathtub, kitchen sink, washing machine). Every size is a
nominal catalogue dimension and says so in SIZE_BASIS, in each row and in both
tool docstrings; no standard is cited because none fixes these sizes.

Each item is typed block_define primitives on layer 0 inside [0, w] x [0, d],
origin at the back-left corner with the wall side along +X - a test holds all
twenty to their footprint. catalogue() searches name, family and the English
and Turkish labels, ignoring case and Turkish diacritics (buzdolabi finds
buzdolabı). insert_item() defines ARCH_<NAME> once, creates the furniture or
sanitary layer from its ARCH_LAYERS row when missing (live ActiveX refuses a
missing layer), inserts with an invisible ITEM attribute, and refuses an
unknown name with the nearest catalogue names before any write.

SECTION 25 opens on this branch with the two tools, alias records (INSERT,
ADCENTER, TOOLPALETTES and English/Turkish synonyms), a regenerated inventory,
the README snapshot at 231 tools, and the branch-local group snapshot
(entity_query 10, entity_creation 19) that the track F merge recomputes.

No new ActiveX call: insert_item composes the existing layer_list,
layer_create, block_list, block_define and block_insert members. Run once live
through ComBackend on AutoCAD 2026 (scratch document, closed after): all
twenty items defined and inserted, a second double_bed insert reported
defined=false, ITEM read back as double_bed, 20 ARCH_* blocks, 21 INSERTs on
A-FURN-E-N / A-SANR-E-N, both layers created at ACI 5 / 0.18 mm.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-23 22:35:44 +03:00
Umut CANandClaude Opus 5.5 67d578e09a feat(task-5): planar faces, measured room labels and the foreign-plan reader
engineering/arch/faces.py finds the closed faces a set of straight segments
bounds: endpoints within tol merge, every segment splits where another crosses
or touches it (the T-junction a partition makes), dangling edges are pruned,
and the half-edge walk turns to the next edge clockwise so every bounded face
comes out counter-clockwise. A piece nested inside a face of another piece is
a hole in it, so a ring of walls measures its body and a room with a column
measures its floor. Areas come from engineering.measure.polygon_area_perimeter,
the arithmetic analysis_measure_entity reports.

engineering/arch/rooms.py labels a room with the area of the face its point
lies in - a 4000 x 5000 room between 200 mm walls reads 20.00 m2, never the
axis area - and writes the room record onto the name text. Opening records
close door and window gaps along the wall faces, so two rooms either side of a
door stay two rooms. rooms_detect reads any plan's lines, drops faces under
min_area and thin wall bodies (mean width under 600 mm, a declared heuristic),
reports confidence 1.0 for faces enclosed only by our wall layer and 0.6
otherwise, lists arcs as skipped rather than flattening them, and never
modifies the drawing.

Two hardenings beyond the plan text, each with a test: read_records pages the
current space once and filters to the arch layers locally (the plan paged it
once per arch layer - twelve full scans, each a cross-process call per entity
on a live seat), keeping the same layer-then-drawing order; and an opening
record whose offset falls outside its wall (a stale record) is listed in
omitted instead of raising out of the read-only detector.

No new ActiveX member is called: both tools compose entity_list,
entity_get/set_xdata, layer_list and draw_prims, whose COM paths are already
live-verified.

SECTION 25 opens on this branch with arch_room and arch_rooms_detect under
the rooms marker; until Task 9 gives arch its group they file under
entity_creation and entity_query. Alias records, a regenerated inventory, the
README snapshot at 231 tools in 25 groups and the frozen group-size snapshot
follow.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-23 22:33:37 +03:00
Umut CANandClaude Opus 5.5 dae48d9037 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
2026-09-23 22:33:31 +03:00
Umut CANandClaude Opus 5.5 d5c5e90406 feat(arch): the plan model and its foundation - layers, materials, EN/TR, two generalisations
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
2026-09-23 22:27:00 +03:00