Files
Aislo/resources/tester/test_241_development.py
T
eomsangdonandClaude Opus 5.5 bb5f07eb0c feat(377-2 B07 전개도): 좌측 「전개도」 카드 · 목록 · 단건 · 확정본 kind — 늘 카드 · 구조물 종류마다 하위 묶음(이름 줄 + 형태 단추) · 목록 맨 뒤 · 전개도 id 면 측점별 설계 실어 그림
- Router_Support _drawing_list 맨 뒤 development_list · _read_drawing 분기 · 확정본 kind development
- Router 단건 = _designs_by_chainage · Schema · Api_Fetch kind development
- UI_Development.ts 새 카드(groupPlanSheets 재사용) · test_311 접힘 대조에 전개도 카드(기본 펼침)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
2026-10-17 14:53:45 +09:00

251 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""B07 전개도(요구 241-2 · 3 · PLAN 377-1) — 측점 표기 · 묶음 · 거리 · 면적 · 계 · 그림 · DXF.
견본(그림 241 콘크리트포장): No.22+00~No.26+05 = 85 m × B 3.5 = 297.5 ㎡ (측점 간격 20 m).
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from B05_Profile.B05_Profile_Structures_Repository import structures_file_path
from B07_DesignDetail import B07_DesignDetail_Engine_Cad_Development as dev
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Development_Draw import (
FILL_LAYER_ID,
build_development_drawing,
)
from B07_DesignDetail.B07_DesignDetail_Engine_Frame_Export import drawing_to_dxf
POINTS = [
{ # 관 Φ800 — 유입 찰 H2.0(전 3 · 후 3) · 유출 메 높이 빈칸(관경 최소 1.3) · 추가 흙막이 1 · 2
"chainage_m": 88.0,
"options": {
"pipe_diameter_mm": 800,
"inlet_type": "기슭막이",
"inlet_revet_form": "돌쌓기(찰)",
"inlet_revet_height_m": 2.0,
"inlet_revet_before_m": 3,
"inlet_revet_after_m": 3,
"outlet_type": "기슭막이",
"outlet_revet_form": "돌쌓기(메)",
"outlet_revet_length_m": 8, # 전 · 후 빈칸 → 반씩
"soil_add_1_form": "돌쌓기(메)",
"soil_add_1_height": 1.5,
"soil_add_1_before": 2,
"soil_add_1_after": 4.5,
"soil_add_2_form": "돌쌓기(메)",
"soil_add_2_height": 1.5,
},
},
{ # 박스 — 유출 흙막이(높이 빈칸 = 박스 높이 2.5) · 유입 날개벽
"chainage_m": 300.0,
"facility": "box_culvert",
"options": {
"body_height_m": 2.5,
"wing_in": "날개벽",
"wing_out": "흙막이",
"outlet_revet_form": "돌쌓기(메)",
"outlet_revet_before_m": 3,
"outlet_revet_after_m": 3,
},
},
]
STRUCTURES = [
{ # 콘크리트 포장 No.22+00 ~ No.26+05 · 폭 3.5
"type_id": "pavement_concrete",
"placement": "interval",
"start_m": 440.0,
"end_m": 525.0,
"options": {"width_m": 3.5},
},
{ # 폭 빈칸 = 횡단 노폭 구간 평균
"type_id": "pavement_concrete",
"placement": "interval",
"start_m": 720.0,
"end_m": 760.0,
"options": {},
},
{ # 흙막이 메 H2.0 + 추가 단 하나(찰 H1.0 = 별도 박스 · 찰 장)
"type_id": "soil_guard",
"placement": "interval",
"start_m": 80.0,
"end_m": 88.0,
"options": {
"form": "돌쌓기(메)",
"height_m": 2.0,
"tier_1_form": "돌쌓기(찰)",
"tier_1_height": 1.0,
},
},
{ # 숨김 벽 종류(돌쌓기(메)) = 흙막이 묶음 · 높이 빈칸
"type_id": "masonry_dry",
"placement": "interval",
"start_m": 100.0,
"end_m": 110.0,
"options": {"height_m": None},
},
{ # 옹벽 — 형태 = wall_form(form 은 형식)
"type_id": "retaining_wall",
"placement": "interval",
"start_m": 200.0,
"end_m": 210.0,
"options": {"form": "중력식", "wall_form": "콘크리트", "height_m": 3.0},
},
]
DESIGNS = {
88.0: {"extra_walls": [{"side": "outlet"}]}, # 유출 다단 1 → 추가 흙막이 2 는 안 섬
720.0: {"carriageway_width_m": 4.0},
740.0: {"carriageway_width_m": 5.0},
760.0: {"carriageway_width_m": 4.5},
}
@pytest.fixture
def root(tmp_path: Path) -> Path:
found = tmp_path / "proj"
edits = found / "B04_PreProcess/drainage/edits"
edits.mkdir(parents=True)
(edits / "pipe_points.json").write_text(
json.dumps({"points": [{**p, "source": "user"} for p in POINTS]}, ensure_ascii=False),
encoding="utf-8",
)
path = Path(structures_file_path(str(found)))
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
json.dumps({"revision": 1, "structures": STRUCTURES}, ensure_ascii=False), encoding="utf-8"
)
return found
def _sheet(sheets, sheet_id):
return next(s for s in sheets if s["id"] == sheet_id)
def test_측점_표기():
assert dev.station_text(440.0, 20) == "No.22+00"
assert dev.station_text(525.0, 20) == "No.26+05"
assert dev.station_text(88.0, 20) == "No.04+08"
assert dev.station_text(445.5, 20) == "No.22+05.5"
assert dev.station_text(459.98, 20) == "No.23+00"
def test_그림_예_거리_면적_계():
rows = [
dev._row("concrete", "", 440.0, 525.0, 3.5, "a"),
dev._row("concrete", "", 720.0, 860.0, 3.5, "b"),
]
lines = dev.table_rows(rows, 20.0)
assert lines[0] == {
"station": "No.22+00~No.26+05",
"distance": 85.0,
"height": 3.5,
"area": 297.5,
}
assert lines[1]["area"] == 490.0
drawing = build_development_drawing(
"development_concrete",
{
"title": "콘크리트포장 전개도",
"group": "concrete",
"form": "",
"rows": rows,
"missing": 0,
},
20.0,
)
table = next(e for e in drawing["entities"] if e["type"] == "Table")
cells = table["shapeData"]["cells"]
assert cells[0][2]["text"] == "폭"
assert [c["text"] for c in cells[2]] == ["No.22+00~No.26+05", "85.0", "3.5", "297.5"]
assert [c["text"] for c in cells[-1]] == ["계", "225.0", "", "787.5"]
def test_묶음_거리_높이(root):
sheets = dev.development_sheets(dev.development_rows(root, DESIGNS))
ids = [s["id"] for s in sheets]
# 차례 = 관보호공 → BOX → 흙막이 → 옹벽 → 콘크리트포장 · 형태마다 한 장
assert ids == [
"development_pipe0",
"development_pipe1",
"development_box1",
"development_soil0",
"development_soil1",
"development_wall2",
"development_concrete",
]
pipe = _sheet(sheets, "development_pipe0")
assert pipe["title"] == "관보호공 돌쌓기(찰) 전개도"
assert [(r["start_m"], r["end_m"], r["height_m"]) for r in pipe["rows"]] == [(85.0, 91.0, 2.0)]
meh = _sheet(sheets, "development_pipe1")["rows"]
# 유출 길이 8 반씩(84~92 · 높이 빈칸 = 관경 최소 1.3) · 추가 흙막이 1(86~92.5) · 2 는 다단 밖
assert [(r["start_m"], r["end_m"], r["height_m"]) for r in meh] == [
(84.0, 92.0, 1.3),
(86.0, 92.5, 1.5),
]
box = _sheet(sheets, "development_box1")
assert box["title"] == "BOX암거 흙막이 돌쌓기(메) 전개도"
assert [(r["start_m"], r["end_m"], r["height_m"]) for r in box["rows"]] == [(297.0, 303.0, 2.5)]
# 추가 단 = 별도 박스(자기 형태 장) · 숨김 돌쌓기(메) = 흙막이 묶음 · 높이 빈칸
assert [(r["height_m"]) for r in _sheet(sheets, "development_soil0")["rows"]] == [1.0]
soil = _sheet(sheets, "development_soil1")["rows"]
assert [(r["start_m"], r["height_m"]) for r in soil] == [(80.0, 2.0), (100.0, None)]
wall = _sheet(sheets, "development_wall2")
assert wall["title"] == "옹벽 콘크리트 전개도" and wall["rows"][0]["height_m"] == 3.0
pave = _sheet(sheets, "development_concrete")
assert pave["title"] == "콘크리트포장 전개도"
assert [r["height_m"] for r in pave["rows"]] == [3.5, 4.5] # 빈 폭 = (4 + 5 + 4.5) / 3
def test_설계_없으면_단수_안_거름_목록(root):
rows = dev.development_rows(root)
meh = [r for r in rows if r["group"] == "pipe" and r["form"] == "돌쌓기(메)"]
assert len(meh) == 3 # 추가 흙막이 2 도(단 수 모름) · 2 는 길이 빈칸
sheet = _sheet(dev.development_sheets(rows), "development_pipe1")
assert sheet["missing"] == 1 and len(sheet["rows"]) == 2
items = dev.development_list(root, {})
assert {i.kind for i in items} == {"development"}
assert (items[0].plan_kind, items[0].plan_kind_name) == ("development_pipe", "관보호공")
assert items[0].label == "관보호공 돌쌓기(찰) 전개도"
def test_넘치면_2장():
rows = [dev._row("soil", "돌망태", i * 20.0, i * 20.0 + 6, 2.0, str(i)) for i in range(35)]
sheets = dev.development_sheets(rows)
assert [s["id"] for s in sheets] == ["development_soil3", "development_soil3_2"]
assert [len(s["rows"]) for s in sheets] == [30, 5]
assert sheets[1]["title"] == "흙막이 돌망태 전개도 (2)"
def test_높이_빈칸_박스와_미입력_글_무늬_DXF(root):
sheets = dev.development_sheets(dev.development_rows(root, DESIGNS))
sheet = _sheet(sheets, "development_soil1")
drawing = build_development_drawing(sheet["id"], sheet, 20.0)
texts = [e["shapeData"]["label"] for e in drawing["entities"] if e["type"] == "Text"]
assert "흙막이 돌쌓기(메) 전개도" in texts
assert "No.04+00" in texts and "No.05+10" in texts
assert "H=2.0" in texts and "H=미입력" in texts and "L=8.0m" in texts
fills = [e for e in drawing["entities"] if e["layerId"] == FILL_LAYER_ID]
assert len(fills) > 10 # 돌 무늬
table = next(e for e in drawing["entities"] if e["type"] == "Table")
assert [c["text"] for c in table["shapeData"]["cells"][-1]] == ["계", "18.0", "", "16.0"]
# 같은 자료 = 같은 그림(씨앗 고정)
assert build_development_drawing(sheet["id"], sheet, 20.0) == drawing
dxf, _skipped = drawing_to_dxf(drawing)
text = dxf.decode("utf-8", "ignore")
assert "b07-dev-fill" in text and "No.04+00" in text and "297.5" not in text
assert text.count("LINE\r\n") > 50 # 돌 무늬 = 선(DXF 에도 남음)
def test_목록_전_장_없으면_빈_장(root, tmp_path):
long_path = tmp_path / "long.json"
long_path.write_text(json.dumps({"stations": []}), encoding="utf-8")
kind, label, drawing, confirmed, _ = dev.read_development(
root, long_path, "development_pipe1_3", DESIGNS
)
assert kind == "development" and label == "관보호공 돌쌓기(메) 전개도" and not confirmed
with pytest.raises(ValueError):
dev.read_development(root, long_path, "development_x-1", DESIGNS)