- 뒷길이를 안 고른 돌쌓기 장: 표가 쓴 그 뒷길이(양식 장은 양식이 푼 값, 전개 장은 기본 45㎝)로 그림 - 「⚠ 뒷길이를 안 골라 기본 N㎝ 로 섰습니다」 한 줄을 한 곳에 두고 표 사유·그림이 같이 씀(골막이 문구도 같은 함수) - 돌쌓기 표 사유에 그 한 줄이 새로 붙음 — 전에는 기본값인 줄 표에 안 드러났음 - 그림 까닭은 정말 못 그리는 장(큰돌쌓기 직경 축·옹벽)만 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
103 lines
3.7 KiB
Python
103 lines
3.7 KiB
Python
"""구조물도 상단 그림 — 그림 치수가 **수량표와 같은 한 벌**인가 (PLAN 12장).
|
||
|
||
⚠ 옛 B07 그림은 두께 식을 따로 적어, 상부·하부 두께를 넣어도 **그림만 옛 두께**였음.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
if str(ROOT) not in sys.path:
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
from B08_Quantity.B08_Quantity_Engine_StructureFigure import ( # noqa: E402
|
||
attach_figures,
|
||
build_figure,
|
||
)
|
||
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import ( # noqa: E402
|
||
back_length_default_note,
|
||
stone_masonry,
|
||
)
|
||
|
||
|
||
def _sheet(**options) -> dict:
|
||
return {
|
||
"title": "돌쌓기(메)",
|
||
"type_id": "masonry_dry",
|
||
"height_m": 2.0,
|
||
"options": {"height_m": 2.0, **options},
|
||
}
|
||
|
||
|
||
def _paths(shapes: list[dict]) -> list[dict]:
|
||
return [shape for shape in shapes if shape["kind"] == "path"]
|
||
|
||
|
||
def _area(points: list[list[float]]) -> float:
|
||
return abs(sum(x0 * y1 - x1 * y0 for (x0, y0), (x1, y1) in zip(points, points[1:]))) / 2
|
||
|
||
|
||
def test_벽_두께는_사용자가_넣은_값을_따른다() -> None:
|
||
wall = _paths(build_figure(_sheet(back_len_cm=35, face_slope_ratio=0.3)))[0]["points"]
|
||
# 식: 상부 0.35+0.30=0.65 · 하부 0.65+0.30×(2−1)=0.95 · 기울기 0.3 → 앞면 위 끝 0.6
|
||
assert wall[:4] == [[0.0, 0.0], [0.6, 2.0], [1.25, 2.0], [0.95, 0.0]]
|
||
given = _paths(
|
||
build_figure(
|
||
_sheet(
|
||
back_len_cm=35, face_slope_ratio=0.3, thickness_top_m=0.5, thickness_bottom_m=1.2
|
||
)
|
||
)
|
||
)[0]["points"]
|
||
assert given[2] == [1.1, 2.0] and given[3] == [1.2, 0.0]
|
||
|
||
|
||
def test_터파기_넓이는_수량식과_같다() -> None:
|
||
dig = _paths(build_figure(_sheet(back_len_cm=35, face_slope_ratio=0.3)))[1]
|
||
assert dig["dash"]
|
||
assert abs(_area(dig["points"]) - 2.0 * ((0.65 + 0.95) / 2 + 0.2)) < 1e-9
|
||
|
||
|
||
def test_못_그리는_장은_그림_대신_까닭이_실린다() -> None:
|
||
payload = {
|
||
"sheets": [
|
||
_sheet(),
|
||
{**_sheet(stone_cm="60~80"), "type_id": "boulder_masonry"},
|
||
_sheet(back_len_cm=45),
|
||
]
|
||
}
|
||
attach_figures(payload)
|
||
no_back, boulder, drawn = payload["sheets"]
|
||
# 뒷길이 미정은 기본값이 있어 **그림이 섬**(2026-09-14 브레인 판정) — 까닭은 직경 축만.
|
||
assert no_back["figure_reason"] is None and no_back["figure"]
|
||
assert boulder["figure"] is None and "직경" in boulder["figure_reason"]
|
||
assert drawn["figure_reason"] is None and drawn["figure"]
|
||
|
||
|
||
def _texts(shapes: list[dict]) -> list[str]:
|
||
return [shape["text"] for shape in shapes if shape["kind"] == "text"]
|
||
|
||
|
||
def test_기본값으로_그린_장은_표_사유와_같은_말을_적는다() -> None:
|
||
options = {"height_m": 2.0}
|
||
_rows, table_notes = stone_masonry(2.0, 1.0, options, wet=False)
|
||
table_default = [note for note in table_notes if "뒷길이를 안 골라" in note]
|
||
assert table_default == [back_length_default_note(options, 45)]
|
||
|
||
texts = _texts(build_figure(_sheet()))
|
||
assert table_default[0] in texts
|
||
assert "뒷길이 ℓ₃ = 45㎝ (기본)" in texts
|
||
# 두께도 기본 45 로 — 상부 0.45+0.30
|
||
assert "상부 0.75 m" in texts
|
||
|
||
chosen = _texts(build_figure(_sheet(back_len_cm=45)))
|
||
assert "뒷길이 ℓ₃ = 45㎝" in chosen and not any("안 골라" in text for text in chosen)
|
||
assert back_length_default_note({"back_len_cm": 45}, 45) is None
|
||
|
||
|
||
def test_양식_장은_양식이_푼_뒷길이로_그린다() -> None:
|
||
sheet = {**_sheet(), "formula_sheet": {"vars": {"L3": 55}}}
|
||
texts = _texts(build_figure(sheet))
|
||
assert "뒷길이 ℓ₃ = 55㎝ (기본)" in texts and "상부 0.85 m" in texts
|