Files
autocad-mcp-pro/tests/test_arch_openings.py
T
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

95 lines
3.9 KiB
Python

"""Door and window symbols: hinge, leaf, swing arc, frame and glazing.
The wall runs along +X from the origin, 200 mm thick and centred, so its left
face is y = +100 and its right face y = -100. The door is 900 wide at offset
1000: its jambs are x = 1000 and x = 1900. For each swing/hand pair the hinge,
the open leaf and the arc angles are written out below by hand.
"""
from __future__ import annotations
import pytest
from engineering.arch.model import Opening, Wall
from engineering.arch.openings import opening_prims
from engineering.mech.primitives import Arc, Line
WALL = Wall(id="a", axis=((0.0, 0.0), (5000.0, 0.0)), thickness=200.0)
def door(swing: str, hand: str) -> Opening:
return Opening(
id="d1", wall="a", kind="door", offset=1000.0, width=900.0, swing=swing, hand=hand
)
def pt(p) -> tuple[float, float]:
return (round(p[0], 9) + 0.0, round(p[1], 9) + 0.0)
@pytest.mark.parametrize(
("swing", "hand", "hinge", "leaf_end", "start_deg", "end_deg"),
[
# in = the axis's left (+Y). Facing the wall from +Y, the viewer's left is +X.
("in", "left", (1900.0, 100.0), (1900.0, 1000.0), 90.0, 180.0),
("in", "right", (1000.0, 100.0), (1000.0, 1000.0), 0.0, 90.0),
# out = the axis's right (-Y). Facing the wall from -Y, the viewer's left is -X.
("out", "left", (1000.0, -100.0), (1000.0, -1000.0), 270.0, 0.0),
("out", "right", (1900.0, -100.0), (1900.0, -1000.0), 180.0, 270.0),
],
)
def test_a_door_hinges_on_its_hand_and_swings_a_quarter_arc_to_its_side(
swing, hand, hinge, leaf_end, start_deg, end_deg
):
leaf, arc = opening_prims(WALL, door(swing, hand))
assert isinstance(leaf, Line) and leaf.role == "door"
assert (pt(leaf.p1), pt(leaf.p2)) == (hinge, leaf_end)
assert isinstance(arc, Arc) and arc.role == "door"
assert pt(arc.center) == hinge
assert arc.radius == 900.0
assert (round(arc.start_deg, 9), round(arc.end_deg, 9)) == (start_deg, end_deg)
def test_the_leaf_is_as_long_as_the_opening_is_wide_and_square_to_the_wall():
leaf, _arc = opening_prims(WALL, door("in", "left"))
dx = leaf.p2[0] - leaf.p1[0]
dy = leaf.p2[1] - leaf.p1[1]
assert (dx, dy) == (0.0, 900.0)
def test_a_door_on_a_second_segment_is_placed_along_the_whole_axis():
bent = Wall(id="b", axis=((0.0, 0.0), (4000.0, 0.0), (4000.0, 3000.0)), thickness=200.0)
op = Opening(
id="d2", wall="b", kind="door", offset=4500.0, width=800.0, swing="in", hand="right"
)
leaf, arc = opening_prims(bent, op)
# segment 1 runs +Y from (4000, 0); its left normal is -X, so "in" is x < 4000
# and its left face is x = 3900. Facing +X from the left side, the viewer's
# left is +Y, so a right hand hinges at the near jamb, y = 500.
assert pt(leaf.p1) == (3900.0, 500.0)
assert pt(leaf.p2) == (3100.0, 500.0)
assert (round(arc.start_deg, 9), round(arc.end_deg, 9)) == (90.0, 180.0)
def test_a_window_is_two_frame_lines_and_two_glazing_lines():
window = Opening(id="w1", wall="a", kind="window", offset=2500.0, width=1200.0)
prims = opening_prims(WALL, window)
assert len(prims) == 4
assert all(isinstance(p, Line) and p.role == "window" for p in prims)
assert [(pt(p.p1), pt(p.p2)) for p in prims] == [
((2500.0, 100.0), (3700.0, 100.0)), # frame, left face
((2500.0, -100.0), (3700.0, -100.0)), # frame, right face
((2500.0, round(-100.0 + 200.0 / 3.0, 9)), (3700.0, round(-100.0 + 200.0 / 3.0, 9))),
((2500.0, round(-100.0 + 400.0 / 3.0, 9)), (3700.0, round(-100.0 + 400.0 / 3.0, 9))),
]
def test_an_opening_of_another_wall_is_refused():
with pytest.raises(ValueError, match="hosted in wall 'z'"):
opening_prims(WALL, Opening(id="d1", wall="z", kind="door", offset=0.0, width=900.0))
def test_an_unknown_swing_is_refused_with_the_list():
with pytest.raises(ValueError, match="swings are in, out"):
opening_prims(WALL, door("sideways", "left"))