# -*- 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", "Text"] # 지시선 없음(169) 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"]) # 구조물마다 조합 줄 → 좌측 패널 옵션 줄(166-1) · 옵션 없는 옹벽은 조합 줄만 assert labels[:5] == ["배수관", "Φ800m/m", "L=8m", "NO.2", _A["name"]] assert labels[5].startswith("관: 관경 800") and labels[-1] == _C["name"] assert all(": " in line for line in labels[5:-1]) # ── 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