Files
Aislo/resources/tester/test_265_note_cad.py
T
eomsangdonandClaude Opus 5.5 a1d63ebfd6 feat(B06): 상세 창 CAD 칸 표 아래 접기 · 구조물 조합 줄 미리 넣음(265-1 · 265-2)
- 상세 창 차례 = 그림 → 표 → CAD 칸 · 칸 머리 한 줄 접기(공용 ui-collapsible) · 열 때 일반 측점 접힘 · 구조물 측점 폄 · 처음 펼 때 칸 만듦
- 미리 넣는 글 = 그 측점 구조물(관 지점 · 구조물 배치 기준 측점)마다 지정된 구조물 조합 이름 줄 · 미지정 = 「이름 조합 미지정」 · 배관 = 원형 마크(본체 줄 없음) · 옛 관보호공 · 관유출부보강 짜 맞춘 글 버림
- B07 짝(CulvertNote.py) 같은 규칙 · 정본 structures.json · 구조물정의 이름까지 읽음
- 시험 test_265_note_cad 넓힘 · test_260 · test_150 기대값

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL
2026-10-11 16:48:15 +09:00

229 lines
9.9 KiB
Python

# -*- coding: utf-8 -*-
"""PLAN 265 — B06 [상세] 표 위 CAD 칸 · 접기 · 차례 · 구조물 글 · 관 길이 정수.
- 265-1 상세 창 차례 = 그림 → 표 → CAD 칸 · CAD 칸 접기(일반 측점 접힘 · 구조물 측점 폄).
- 265-2 미리 넣는 글 = 그 측점 구조물마다 지정된 구조물 조합 이름 줄 · 미지정 · 배관 = 원형 마크
(TS = 파이썬 짝 · `helper_260_note_cad.cjs` 가 실제 TS 를 Node 로 돌림).
- 265-3 원형 마크 L = 사사오입 정수(TS `pipeLengthWhole` = 파이썬 짝 같은 값).
./venv/Scripts/python.exe -m pytest resources/tester/test_265_note_cad.py -q
"""
import json
import subprocess
import sys
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parents[2]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
from B05_Profile.B05_Profile_Structures_Repository import structures_file_path # noqa: E402
from B06_Section.B06_Section_Engine_Culvert import _culvert_set, pipe_points_file # noqa: E402
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_CulvertNote import ( # noqa: E402
load_note_sources,
note_entities,
structure_note_lines,
with_structure_note,
)
HERE = Path(__file__).resolve().parent
_PIPE = {"pipe_diameter_mm": 800}
def _helper(mode: str, payload) -> list:
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", str(HERE / "helper_260_note_cad.cjs"), mode],
input=json.dumps(payload, ensure_ascii=False),
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def _labels(entities: list[dict]) -> list[str]:
return [e["shapeData"]["label"] for e in entities if e["type"] == "Text"]
def _strip(entities: list[dict]) -> list[dict]:
return [{k: v for k, v in e.items() if k != "id"} for e in entities]
def _case(things: list, chainage: float, culvert: dict | None, design: dict) -> dict:
return {
"section": {"chainage_m": chainage, "culvert": culvert, "design": design},
"station": "NO.2",
"things": things,
"chainage": chainage,
"interval": 20.0,
}
def _read(rel: str) -> str:
return (PROJECT_ROOT / rel).read_text(encoding="utf-8")
# ── 265-1 차례 · 접기 ──────────────────────────────────────────────────
def test_상세_창_차례는_그림_표_CAD_칸():
detail = _read("B06_Section/B06_Section_UI_Cross_Detail.ts")
assert "body.append(cad, tableBox, noteBox);" in detail
# CAD 칸 = 공용 접기 꼴(머리 한 줄 · 캐럿) · 펼 때 한 번 만듦
assert 'noteBox.className = "b06-detail__notebox ui-collapsible";' in detail
assert 'noteHead.className = "b06-detail__notehead ui-collapsible__title";' in detail
assert 'if (!noteBox.classList.toggle("is-collapsed")) openNote();' in detail
def test_열_때_기본_일반_측점_접힘_구조물_측점_폄():
detail = _read("B06_Section/B06_Section_UI_Cross_Detail.ts")
start = detail.index('noteBox.classList.add("is-collapsed");')
rule = detail[start : detail.index("const cad = ", start)]
# 접힘으로 시작 → 그 측점에 놓인 구조물(`thingsAt`)이 있으면 폄 · 사용자가 먼저 누르면 그대로
assert "thingsAt(list, section.chainage_m).length" in rule
assert 'noteBox.classList.remove("is-collapsed");' in rule and "touched" in rule
# 판정 = 놓인 구조물 수(구조물 측점 1 이상 · 일반 측점 0) — 아래 거울 시험 `at`
[structure, plain] = _helper(
"mirror",
[_case(_THINGS, 40.0, None, {}), _case(_THINGS, 20.0, None, {})],
)
assert structure["at"] == 5 and plain["at"] == 0
# ── 265-2 구조물 조합 줄 ───────────────────────────────────────────────
_A = {"layer": "system", "owner": None, "key": "SS000055", "name": "기슭막이 메쌓기 1.2m 기초무"}
_B = {"layer": "system", "owner": None, "key": "SS000070", "name": "돌붙임 찰붙임"}
_C = {"layer": "company", "owner": "3", "key": "SC3-000001", "name": "옹벽 콘크리트 H=2.0"}
_BODY = {"layer": "system", "owner": None, "key": "SS000001", "name": "흄관 1200"}
_THINGS = [
# 배관 = 본체(옛 summary_item · 「관」 grv6lny) 줄 없음 · 보호공 · 도수로 조합만
{
"name": "배관",
"pipe": True,
"at": 40.2,
"summary_item": _BODY,
"group_items": {"grv6lny": _BODY, "gnbf9m9": _A, "gchuteadd1": _B},
},
# 옹벽 = 본체 + 그룹(같은 조합 = 한 줄)
{"name": "옹벽", "pipe": False, "at": 40.0, "summary_item": _C, "group_items": {"gx": _C}},
# 조합 미지정 구조물
{"name": "돌쌓기(찰)", "pipe": False, "at": 39.6},
# 미지정 배관(원형 마크가 대신 · 줄 없음) · 모양 틀린 참조 = 미지정
{"name": "배관", "pipe": True, "at": 40.0, "group_items": {"bad id": _A}},
{"name": "가드레일", "pipe": False, "at": 40.0, "summary_item": {"key": ""}},
# 이웃 측점 구조물 = 뺌
{"name": "옹벽", "pipe": False, "at": 40.6, "summary_item": _C},
]
_LINES = [
"기슭막이 메쌓기 1.2m 기초무",
"돌붙임 찰붙임",
"옹벽 콘크리트 H=2.0",
"돌쌓기(찰) 조합 미지정",
"가드레일 조합 미지정",
]
def test_구조물_여럿_글은_조합_이름_미지정_배관_본체_뺌_TS_파이썬_같음():
culvert = {**_culvert_set(_PIPE), "chainage_m": 40.2}
cases = [
_case(_THINGS, 40.0, culvert, {"pipe_length_m": 8.5}),
_case(_THINGS, 40.0, None, {}),
_case(_THINGS[5:], 40.0, None, {}),
]
mirrored = _helper("mirror", cases)
for case, ts in zip(cases, mirrored, strict=True):
section = case["section"]
lines = structure_note_lines(case["things"], 40.0)
py = note_entities(40.0, section["culvert"], section["design"], "NO.2", lines)
assert ts["lines"] == lines
assert _strip(ts["entities"]) == _strip(py)
full, bare, none = mirrored
assert full["lines"] == _LINES
# 배관 = 원형 마크(원 안 배수관 · Φ · L 정수) · 오른쪽 측점 + 조합 줄
assert [e["type"] for e in full["entities"][:2]] == ["Circle", "Line"]
assert _labels(full["entities"]) == ["배수관", "Φ800m/m", "L=9m", "NO.2", *_LINES]
assert "흄관 1200" not in _labels(full["entities"])
# 원형 마크 없음(관 없는 구조물 측점) = 측점 + 줄 · 구조물 없는 측점 = 빈 도면
assert _labels(bare["entities"]) == ["NO.2", *_LINES]
assert none["entities"] == [] and none["at"] == 0
def test_글_줄_재료는_관_지점_구조물_배치_종류_이름():
payload = {
"pipes": [
{"chainage_m": 40.2, "facility": "pipe", "group_items": {"gnbf9m9": _A}},
{"chainage_m": 60.0, "facility": "box_culvert"},
],
"structures": [
{"type_id": "retaining_wall", "chainage_m": 40.0, "summary_item": _C},
{"type_id": "guardrail", "start_m": 30.0, "end_m": 80.0},
{"type_id": "mystery", "chainage_m": None, "start_m": None},
],
"types": [
{"type_id": "pipe", "name": "배관"},
{"type_id": "box_culvert", "name": "BOX암거"},
{"type_id": "retaining_wall", "name": "옹벽"},
{"type_id": "guardrail", "name": "가드레일"},
],
}
things = _helper("load", payload)
assert [(t["name"], t["pipe"], t["at"]) for t in things] == [
("배관", True, 40.2),
("BOX암거", False, 60.0),
("옹벽", False, 40.0),
("가드레일", False, 30.0), # 구간형 = 시작 측점(기준 측점 없을 때)
]
assert things[0]["group_items"] == {"gnbf9m9": _A} and things[2]["summary_item"] == _C
def test_파이썬_짝_재료는_정본_관_지점_구조물_배치(tmp_path):
path = pipe_points_file(tmp_path)
path.parent.mkdir(parents=True, exist_ok=True)
point = {"chainage_m": 40.2, "lonlat": [0, 0], "facility": "pipe", "options": _PIPE}
point["group_items"] = {"gnbf9m9": _A}
path.write_text(json.dumps({"points": [point]}, ensure_ascii=False), encoding="utf-8")
spath = Path(structures_file_path(str(tmp_path)))
spath.parent.mkdir(parents=True, exist_ok=True)
wall = {
"structure_id": "w1",
"type_id": "retaining_wall",
"placement": "point",
"chainage_m": 40.0,
"options": {},
"memo": "",
"placement_source": "manual",
"status": "draft",
"revision": 1,
"geometry": None,
"summary_item": _C,
}
doc = {"revision": 1, "structures": [wall]}
spath.write_text(json.dumps(doc, ensure_ascii=False), encoding="utf-8")
sources = load_note_sources(tmp_path)
assert [(t["name"], t["at"]) for t in sources[1]] == [("배관", 40.2), ("옹벽", 40.0)]
seeded = with_structure_note({"pipe_length_m": 8.4}, 40.0, sources, "NO.2")
labels = _labels(seeded["structure_note_cad"]["entities"])
assert labels == ["배수관", "Φ800m/m", "L=8m", "NO.2", _A["name"], _C["name"]]
# ── 265-3 관 길이 정수 ─────────────────────────────────────────────────
@pytest.mark.parametrize(
("length", "label"),
[(8.5, "L=9m"), (8.4, "L=8m"), (8.45, "L=8m"), (7.0, "L=7m"), (None, "L=")],
)
def test_원형_마크_L_은_사사오입_정수_TS_파이썬_같음(length, label):
design = {} if length is None else {"pipe_length_m": length}
culvert = {**_culvert_set(_PIPE), "chainage_m": 40.0}
[ts] = _helper("mirror", [_case([], 40.0, culvert, design)])
py = note_entities(40.0, culvert, design, "NO.2", [])
assert _labels(ts["entities"])[2] == label
assert _labels(py)[2] == label