- 상세 창 차례 = 그림 → 표 → 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
229 lines
9.9 KiB
Python
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
|