feat(524-1): B07 평면도 설정 모달 정리 — 기본 항목 지형도 하나 · 양식 칸 숨김 · 종류 척도 2열 · 항목 그룹 구분선 · 대피소 사토장 자리 지움

- 기본 종류 = 지형도 하나(옛 기본 이관 더하기 지움) · 대피소 조각 · 자리 폼 · plan-marks 끝점 지움
- 모달 양식 칸 = 평면도면 숨김 · 종류 척도 영역 2열 · 항목 3열 유지 + 그룹 구분선(PIECE_GROUPS 한 곳)
- 낡은 시험 수선: 기본 종류가 하나로 줄어 route · layout · landuse · watershed 기본 전제를 add_kind 로 세움 · 대피소 자리 시험 지움

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 14:23:59 +09:00
co-authored by Claude Sonnet 5.5
parent 7e2bc3a8e7
commit a96a60fb86
13 changed files with 126 additions and 435 deletions
@@ -144,9 +144,6 @@ const STUBS = {
currentLanguageIndex: 0,
ui_locales: new Proxy({}, { get: (_, key) => [String(key)] }),
},
"../B05_Profile/B05_Profile_UI_Structures_Fields": {
stationFields: () => ({ wrap: new FakeNode("div"), write: () => {}, read: () => 0 }),
},
};
// ── 서버 길 가짜 — 종류 파일 · 장 목록이 메모리에 ───────────────────────────────
@@ -165,8 +162,6 @@ const state = {
{ id: "k_terrain", name: "지형", pieces: ["terrain"], layout: "plan", scale: 1200 },
{ id: "k_route", name: "노선배치도", pieces: ["route"], layout: "plan", scale: 1200 },
],
marks: [],
station_interval_m: 20,
};
let confirmed = new Set(["plan_k_route"]);
/** 종류 하나 = 장 하나 · 「지형」만 두 장. */
+3 -3
View File
@@ -2,8 +2,8 @@
"""130-1 B07 [평면도 관리] 모달.
못박는 것
- 좌측 「평면도」 = 장 단추 + [평면도 관리] 하나 · 종류 폼 · 대피소 칸 · × 없음.
- 단추 → 공용 모달(큰 창) · 안에 [종류 ▼] 폼 · 평면도 장 블록(종류마다 한 줄 · 작은 장 단추) · 대피소 · 사토장 자리 ·
- 좌측 「평면도」 = 장 단추 + [평면도 관리] 하나 · 종류 폼 · × 없음.
- 단추 → 공용 모달(큰 창) · 안에 [종류 ▼] 폼 · 평면도 장 블록(종류마다 한 줄 · 작은 장 단추) ·
첫 장 미리보기(보기 전용).
- 종류 추가 · 업데이트(축척 포함) · 지우기(폼 안) = 지금 서버 길 · 고칠 때마다 모달 안을 다시 받음.
- 장 단추 → 드롭다운 따라감 · 드롭다운 → 그 종류 첫 장 · 줄 끌어 놓기 = 종류 순서(놓는 즉시 · 140-2).
@@ -52,7 +52,7 @@ def test_단추를_누르면_모달(out):
assert opened["dialogClass"] == "b07-plan-manage"
assert opened["kinds"] == ["k_terrain", "k_route"]
assert opened["sheets"] == ["plan_k_terrain", "plan_k_terrain_2", "plan_k_route"]
assert opened["hasKindForm"] and opened["hasMarks"]
assert opened["hasKindForm"] and not opened["hasMarks"] # 524 대피소 · 사토장 자리 지움
def test_첫_장_미리보기는_보기_전용(out):
@@ -80,4 +80,4 @@ def test_목록_도면명_n_N장(tmp_path: Path):
items = [
i for i in plan_kind_items(tmp_path, {"stations": stations}) if i["plan_kind"] == "terrain"
]
assert [item["label"] for item in items] == [f"평면도(지형) {n}/3장" for n in (1, 2, 3)]
assert [item["label"] for item in items] == [f"평면도(지형도) {n}/3장" for n in (1, 2, 3)]
@@ -23,6 +23,7 @@ sys.path.insert(0, str(ROOT))
from B07_DesignDetail import B07_DesignDetail_Router_Support_Fingerprint as fp # noqa: E402
from B07_DesignDetail.B07_DesignDetail_Router_Support_Basin import _metric_lines # noqa: E402
from B07_DesignDetail.B07_DesignDetail_Router_Support_Plan import ( # noqa: E402
_kinds_dir,
list_kinds,
update_kind,
)
@@ -46,6 +47,14 @@ def test_간격_배수_높이만(tmp_path):
def _watershed(root: Path) -> dict:
folder = _kinds_dir(root)
if not folder.is_dir(): # 기본 종류는 지형도뿐(524-1) — 유역도 종류를 파일로 둠
folder.mkdir(parents=True)
for order, (name, layout) in enumerate((("지형도", "plan"), ("배수유역도", "watershed"))):
document = {"name": name, "pieces": ["contour"], "order": order, "layout": layout}
(folder / f"{'terrain' if layout == 'plan' else 'watershed'}.json").write_text(
json.dumps(document), "utf-8"
)
return next(kind for kind in list_kinds(root) if kind["layout"] == "watershed")
@@ -88,9 +97,9 @@ def test_지문_유역도만(tmp_path, monkeypatch):
def changed(before: dict, after: dict) -> set:
return {key for key in before if before[key] != after[key]}
first = prints()
kind = _watershed(root)
update_kind(root, kind["id"], kind["name"], [], None, 5) # 기본 종류를 파일로 내림 · 같은 값
first = prints()
update_kind(root, kind["id"], kind["name"], [], None, 5) # 같은 값
assert changed(first, prints()) == set()
update_kind(root, kind["id"], kind["name"], [], None, 10)
ten = prints()
+36 -100
View File
@@ -1,14 +1,14 @@
"""B07 계획평면도 종류 · 정보 조각 (PLAN 60장).
① 측점 둘에서 성토사면 외곽선 폭 = 횡단 설계 값(노견 끝 · 설계선이 지반에 닿는 자리)
② 종류 더하기 · 지우기(기본 넷 포함) · 다시 읽어 같음
③ 대피소 · 사토장 자리 — 한 자리에 둘 다
② 종류 더하기 · 지우기(기본 종류 포함) · 다시 읽어 같음 · 기본 = 지형도 하나
③ (없앰 — 대피소 · 사토장 자리는 524-1 에서 지움)
④ 고른 조각만 도면 층으로 얹힘
⑤ 조각 잘게(67장) — 노견선 · 성토 외곽선 · 절토 외곽선 따로 · 기본 넷 처음 체크 = 옛 그림 ·
옛 종류 파일의 slope_outline 펼침 · 종류 업데이트(기본 넷 포함) · 확정본 풀림 · 배수시설 기호
⑤ 조각 잘게(67장) — 노견선 · 성토 외곽선 · 절토 외곽선 따로 · 기본 처음 체크 = 옛 그림 ·
옛 종류 파일의 slope_outline 펼침 · 종류 업데이트(기본 포함) · 확정본 풀림 · 배수시설 기호
⑥ IP 표(94장) — 예시 검산 · 도-분-초 올림 · 장마다 그 장 IP 한 번 · 표 밑 선 자름 · 층
⑦ 용지도 종류(93장) — 기본 종류 · 옛 종류 폴더에 한 번 더함(지우면 안 살아남) · 용지 조각 층
⑧ 배수유역도 종류(109장) — 양식 watershed · 한 장 목록 · 지우기 · 다시 만들기 · IP 표 두 줄 차례 ·
⑦ 용지도 조각(93장) — 용지 조각 층
⑧ 배수유역도 종류(109장) — 새 종류로 더함 · 양식 watershed · 한 장 목록 · 지우기 · 다시 만들기 · IP 표 두 줄 차례 ·
용지도 상자 안 선 0
⑨ 종류 차례(140-2) — 끌어 놓은 차례 그대로 · 다시 읽어 같음 · 앞선 종류가 바뀐 종류만 확정 풂 · 목록 어긋나면 거절
"""
@@ -38,16 +38,13 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Frame_Export import drawing_to_dxf
from B07_DesignDetail.B07_DesignDetail_Router_Support_Io import _read_manifest, _write_manifest
from B07_DesignDetail.B07_DesignDetail_Router_Support_Plan import (
_kinds_dir,
_refuge_items,
add_kind,
delete_kind,
list_kinds,
plan_kind_items,
read_marks,
reorder_kinds,
slope_records,
update_kind,
write_marks,
)
@@ -139,54 +136,23 @@ def test_outline_width_equals_cross_design_at_two_stations(tmp_path: Path) -> No
def test_kinds_add_delete_persist(tmp_path: Path) -> None:
root = tmp_path / "project"
assert [k["name"] for k in list_kinds(root)] == [
"지형",
"노선배치도",
"대피소",
"라이다",
"용지도",
"배수유역도",
]
assert [k["name"] for k in list_kinds(root)] == ["지형도"] # 기본 = 하나
assert [k["id"] for k in DEFAULT_KINDS] == ["terrain"]
assert "refuge" not in DEFAULT_KINDS[0]["pieces"]
added = add_kind(root, "내 평면도", ["centerline", "station", "nope"])
assert added["pieces"] == ["centerline", "station"]
with pytest.raises(ValueError):
add_kind(root, "내 평면도", ["centerline"]) # 같은 이름
with pytest.raises(ValueError):
add_kind(root, "빈 것", [])
delete_kind(root, "terrain") # 기본 넷도 지움
delete_kind(root, "terrain") # 기본 종류도 지움
again = list_kinds(root) # 다시 읽어 같음
assert [k["name"] for k in again] == [
"노선배치도",
"대피소",
"라이다",
"용지도",
"배수유역도",
"내 평면도",
]
assert [k["name"] for k in again] == ["내 평면도"]
assert again[-1]["pieces"] == ["centerline", "station"]
with pytest.raises(FileNotFoundError):
delete_kind(root, "terrain")
for kind in list(again):
delete_kind(root, kind["id"])
assert list_kinds(root) == [] # 다 지우면 비어 있음(기본 넷이 되살아나지 않음)
assert {k["id"] for k in DEFAULT_KINDS} == {
"terrain",
"route",
"layout",
"lidar",
"landuse",
"watershed",
}
def test_refuge_marks_same_spot(tmp_path: Path) -> None:
root = tmp_path / "project"
write_marks(root, [{"chainage_m": 20, "refuge": True, "spoil": True}, {"chainage_m": 5}])
assert read_marks(root) == [{"chainage_m": 20.0, "refuge": True, "spoil": True}]
route = [(0.0, 0.0, 0.0), (40.0, 0.0, 40.0)]
items = _refuge_items(root, route, 0.0, 40.0)["items"]
assert [item["label"] for item in items] == ["대피소 · 사토장"]
assert items[0]["y"] == pytest.approx(20.0)
delete_kind(root, again[0]["id"])
assert list_kinds(root) == [] # 다 지우면 비어 있음(기본이 되살아나지 않음)
def test_drawing_has_only_chosen_pieces() -> None:
@@ -223,23 +189,23 @@ def test_default_kinds_keep_old_picture_and_legacy_file(tmp_path: Path) -> None:
outline = {"road_edge", "fill_outline", "cut_outline"}
# 옛 slope_outline 자리를 나눈 셋이 그대로 채움 · 배수시설은 옛 그림에 없음(용지도는 따로)
for kind in DEFAULT_KINDS:
if kind["id"] not in ("landuse", "watershed"):
assert outline <= set(kind["pieces"]) and "drainage" not in kind["pieces"]
assert outline <= set(kind["pieces"]) and "drainage" not in kind["pieces"]
folder = _kinds_dir(root)
folder.mkdir(parents=True)
(folder / "old.json").write_text(
json.dumps({"name": "옛것", "pieces": ["slope_outline", "station"], "order": 0}), "utf-8"
)
kind = next(k for k in list_kinds(root) if k["id"] == "old") # 용지도는 이관으로 더해짐
kind = next(k for k in list_kinds(root) if k["id"] == "old")
assert set(kind["pieces"]) == outline | {"station"}
def test_update_kind_default_and_release_confirmed(tmp_path: Path) -> None:
root = tmp_path / "project"
route = add_kind(root, "노선배치도", ["centerline"])["id"]
manifest = _read_manifest(root)
manifest["drawings"] = {
"plan_terrain": {"confirmed": True},
"plan_route": {"confirmed": True},
f"plan_{route}": {"confirmed": True},
}
_write_manifest(root, manifest)
update_kind(root, "terrain", "지형2", ["contour", "centerline"]) # 기본 종류 고침
@@ -254,7 +220,7 @@ def test_update_kind_default_and_release_confirmed(tmp_path: Path) -> None:
assert [k["id"] for k in list_kinds(root)][0] == "terrain" # 차례 그대로
drawings = _read_manifest(root)["drawings"]
assert drawings["plan_terrain"]["confirmed"] is False
assert drawings["plan_route"]["confirmed"] is True
assert drawings[f"plan_{route}"]["confirmed"] is True
with pytest.raises(ValueError):
update_kind(root, "terrain", "노선배치도", ["contour"]) # 다른 종류와 같은 이름
with pytest.raises(ValueError):
@@ -320,7 +286,6 @@ def test_ip_table_drawing_clips_lines_under_table(tmp_path: Path) -> None:
assert ip["cells"][2][2]["text"] == "100.00000" # Y = 동(x)
assert ip["cells"][3][1]["text"] == "90-00-00.00"
assert {layer["id"] for layer in drawing["layers"]} >= {layer_id("ip_table")}
assert "ip_table" in next(k for k in DEFAULT_KINDS if k["id"] == "route")["pieces"]
# DXF — 표 → 선 + 글(IP 묶음: 바깥 4 · 세로 10 · 가로 20 · 글 17)
raw, skipped = drawing_to_dxf({"entities": [mine[1]], "layers": drawing["layers"]})
(tmp_path / "ip.dxf").write_bytes(raw)
@@ -329,22 +294,6 @@ def test_ip_table_drawing_clips_lines_under_table(tmp_path: Path) -> None:
assert {"90-00-00.00", "15.00000"} <= {t.dxf.text for t in space.query("TEXT")}
def test_landuse_kind_added_once_to_old_folder(tmp_path: Path) -> None:
root = tmp_path / "project"
folder = _kinds_dir(root)
folder.mkdir(parents=True)
for order, kind in enumerate(DEFAULT_KINDS[:4]): # 93장 앞에 내려 둔 기본 넷
(folder / f"{kind['id']}.json").write_text(
json.dumps({"name": kind["name"], "pieces": kind["pieces"], "order": order}), "utf-8"
)
kinds = list_kinds(root)
assert [k["id"] for k in kinds][-2:] == ["landuse", "watershed"] # 늘어난 기본 종류 둘
assert kinds[-2]["pieces"] == ["contour", "centerline", "parcel", "parcel_info", "emd", "sgg"]
assert len(list_kinds(root)) == 6 # 두 번 읽어도 한 번
delete_kind(root, "landuse")
assert "landuse" not in [k["id"] for k in list_kinds(root)] # 지운 것은 안 살아남
def test_landuse_pieces_become_layers() -> None:
pieces = {
"parcel": {"lines": [[[0.0, 0.0], [10.0, 0.0]]]},
@@ -370,11 +319,11 @@ def test_landuse_pieces_become_layers() -> None:
def test_watershed_kind_layout_one_sheet(tmp_path: Path) -> None:
"""배수유역도 = 기본 종류 · 양식 watershed · 조각 없음 · 장이 여럿이어도 목록은 한 장."""
"""배수유역도 = 새 종류 · 양식 watershed · 조각 없음 · 장이 여럿이어도 목록은 한 장."""
root = tmp_path / "project"
watershed = next(k for k in list_kinds(root) if k["id"] == "watershed")
assert watershed == {
"id": "watershed",
added = add_kind(root, "배수유역도", [], "watershed")
assert next(k for k in list_kinds(root) if k["id"] == added["id"]) == {
"id": added["id"],
"name": "배수유역도",
"pieces": [],
"layout": "watershed",
@@ -387,13 +336,13 @@ def test_watershed_kind_layout_one_sheet(tmp_path: Path) -> None:
]
items = plan_kind_items(root, {"stations": stations})
terrain = [item for item in items if item["plan_kind"] == "terrain"]
mine = [item for item in items if item["plan_kind"] == "watershed"]
mine = [item for item in items if item["plan_kind"] == added["id"]]
assert len(terrain) > 1
assert mine == [
{
"id": "plan_watershed",
"id": f"plan_{added['id']}",
"label": "배수유역도",
"plan_kind": "watershed",
"plan_kind": added["id"],
"plan_kind_name": "배수유역도",
}
]
@@ -402,9 +351,10 @@ def test_watershed_kind_layout_one_sheet(tmp_path: Path) -> None:
def test_watershed_kind_delete_and_add_again(tmp_path: Path) -> None:
"""× 로 지우면 안 살아남 · 「새 종류」 양식 유역도로 다시 만듦 · 업데이트는 양식 그대로."""
root = tmp_path / "project"
delete_kind(root, "watershed")
assert "watershed" not in [k["id"] for k in list_kinds(root)]
assert "watershed" not in [k["id"] for k in list_kinds(root)] # 다시 읽어도 안 살아남
first = add_kind(root, "배수유역도", [], "watershed")
delete_kind(root, first["id"])
assert first["id"] not in [k["id"] for k in list_kinds(root)]
assert first["id"] not in [k["id"] for k in list_kinds(root)] # 다시 읽어도 안 살아남
added = add_kind(root, "배수유역도", ["contour"], "watershed") # 조각은 버림
assert added["layout"] == "watershed" and added["pieces"] == []
again = next(k for k in list_kinds(root) if k["id"] == added["id"])
@@ -418,21 +368,6 @@ def test_watershed_kind_delete_and_add_again(tmp_path: Path) -> None:
add_kind(root, "이상한 양식", ["contour"], "nope")
def test_watershed_kind_added_once_to_folder_with_landuse(tmp_path: Path) -> None:
"""용지도 표시가 있는 옛 폴더 — 배수유역도만 한 번 더해짐 · 지우면 안 살아남."""
root = tmp_path / "project"
folder = _kinds_dir(root)
folder.mkdir(parents=True)
for order, kind in enumerate(DEFAULT_KINDS[:5]):
(folder / f"{kind['id']}.json").write_text(
json.dumps({"name": kind["name"], "pieces": kind["pieces"], "order": order}), "utf-8"
)
(folder / "_landuse_added").touch()
assert [k["id"] for k in list_kinds(root)][-1] == "watershed"
delete_kind(root, "watershed")
assert [k["id"] for k in list_kinds(root)][-1] == "landuse"
def test_ip_table_two_lines_left_to_right_top_to_bottom() -> None:
"""한 줄 셋까지 들어가는 폭 · 묶음 넷 — 첫 줄(1 · 2 · 3) 맨 위 왼 → 오 · 넷째는 아랫줄 왼쪽."""
blocks = [{"name": str(i), "ia": 1.0} for i in range(1, 5)]
@@ -449,22 +384,23 @@ def test_ip_table_two_lines_left_to_right_top_to_bottom() -> None:
def test_reorder_kinds_order_and_release_renumbered(tmp_path: Path) -> None:
root = tmp_path / "project"
second, third = (add_kind(root, name, ["centerline"])["id"] for name in ("둘째", "셋째"))
manifest = _read_manifest(root)
manifest["drawings"] = {
"plan_terrain": {"confirmed": True},
"plan_route": {"confirmed": True},
"plan_layout": {"confirmed": True},
f"plan_{second}": {"confirmed": True},
f"plan_{third}": {"confirmed": True},
}
_write_manifest(root, manifest)
before = [k["id"] for k in list_kinds(root)]
assert before[:3] == ["terrain", "route", "layout"]
after = ["route", "terrain", *before[2:]] # 앞 둘만 바꿈
assert before == ["terrain", second, third]
after = [second, "terrain", third] # 앞 둘만 바꿈
reorder_kinds(root, after)
assert [k["id"] for k in list_kinds(root)] == after # 다시 읽어 같음
drawings = _read_manifest(root)["drawings"]
assert drawings["plan_terrain"]["confirmed"] is False # 번호 바뀜
assert drawings["plan_route"]["confirmed"] is False
assert drawings["plan_layout"]["confirmed"] is True # 앞선 종류 묶음 그대로 = 번호 그대로
assert drawings[f"plan_{second}"]["confirmed"] is False
assert drawings[f"plan_{third}"]["confirmed"] is True # 앞선 종류 묶음 그대로 = 번호 그대로
with pytest.raises(ValueError):
reorder_kinds(root, after[:-1]) # 빠진 종류
with pytest.raises(ValueError):