Files
Aislo/resources/tester/test_119_b07_plan_scale.py
T
eomsangdonandClaude Opus 5.5 f05d77d642 feat(B07): 평면도 축척 여덟 · 실거리 상수 축척화 · 막힌 글 먼 자리(141-2)
- 축척 = 1/500 · 600 · 1,000 · 1,200(기본) · 2,500 · 3,000 · 5,000 · 6,000(지식DB 근거)
- 비탈 눈금 간격 · 대피소 띄움 · 필지 버퍼 · 틈 = 1/1,200 실거리 × 축척 비(종이 거리 같게 · 1/6,000 눈금 10 m)
- 글 후보가 다 막히면 기호에서 2 · 3 배 먼 자리 + 지시선 — 검증_sub8 1/6,000 겹친 쌍 55 → 4
- 시험 test_119 · test_141 늘림

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

194 lines
8.9 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.
"""B07 평면도 종류마다 축척 고르기 (PLAN 119장).
① 축척 목록 1/600 · 1/1,000 · 1/1,200(기본) — 추가 · 업데이트 · 다시 읽어 같음 · 목록 밖 거절 ·
옛 파일(축척 없음 · 목록 밖) = 1/1,200 · 유역도 양식 = 1/6,000 고정
② 1/600 → 장 수 늚 · 1/1,200 = 지금과 같음(기본 인자 장 나눔)
③ 그림 — 축척 글 「S = 1/600」 · 도곽 축척 칸 600 · A3 1200 · 1/1,200 은 기본 인자와 같은 그림
④ 종이 mm → 실거리 환산이 축척 따라감 — 측점 눈금 · 시종점 막대 종이 길이 그대로 ·
IP 표 밑 자름 폭 · 필지 라벨 상자 실거리 절반
"""
import json
import math
from pathlib import Path
import pytest
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_IpTable import _COLUMNS, _ROW_H, ip_table_piece
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Plan import plan_chunks
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanKinds import (
build_plan_kind_drawing,
layer_id,
)
from B07_DesignDetail.B07_DesignDetail_Router_PlanKinds import _state
from B07_DesignDetail.B07_DesignDetail_Router_Support_Landuse import label_rects
from B07_DesignDetail.B07_DesignDetail_Router_Support_Plan import (
_kinds_dir,
add_kind,
list_kinds,
plan_kind_items,
update_kind,
)
_STATIONS = [
{"chainage_m": float(c), "center_x": 0.0, "center_y": float(c)} for c in range(0, 2001, 20)
]
def test_scale_choices_add_update_and_old_files(tmp_path: Path) -> None:
root = tmp_path / "project"
state = _state(root)
# 141-2 — 지식DB 근거 있는 여덟
assert state["scales"] == [500, 600, 1000, 1200, 2500, 3000, 5000, 6000]
assert state["default_scale"] == 1200
assert all(k["scale"] == (6000 if k["layout"] == "watershed" else 1200) for k in state["kinds"])
added = add_kind(root, "용지 600", ["contour"], "plan", 600)
assert added["scale"] == 600
assert next(k for k in list_kinds(root) if k["id"] == added["id"]) == added
assert add_kind(root, "기본 축척", ["contour"])["scale"] == 1200
update_kind(root, added["id"], "용지 1000", ["contour"], 1000)
assert next(k for k in list_kinds(root) if k["id"] == added["id"])["scale"] == 1000
update_kind(root, added["id"], "용지 1000", ["contour"]) # 축척 없이 = 지금 값
assert next(k for k in list_kinds(root) if k["id"] == added["id"])["scale"] == 1000
with pytest.raises(ValueError):
add_kind(root, "이상한 축척", ["contour"], "plan", 700)
with pytest.raises(ValueError):
update_kind(root, added["id"], "용지 1000", ["contour"], 777)
watershed = add_kind(root, "유역 둘", [], "watershed", 600) # 유역도 양식은 고정
assert watershed["scale"] == 6000
folder = _kinds_dir(root)
for stem, extra in (("old", {}), ("odd", {"scale": 777})):
(folder / f"{stem}.json").write_text(
json.dumps({"name": stem, "pieces": ["contour"], "order": 99, **extra}), "utf-8"
)
kinds = {k["id"]: k for k in list_kinds(root)}
assert kinds["old"]["scale"] == 1200 and kinds["odd"]["scale"] == 1200
def test_sheet_count_follows_scale(tmp_path: Path) -> None:
root = tmp_path / "project"
add_kind(root, "용지 600", ["contour"], "plan", 600)
items = plan_kind_items(root, {"stations": _STATIONS})
big = [item for item in items if item["plan_kind_name"] == "용지 600"]
terrain = [item for item in items if item["plan_kind"] == "terrain"]
stations = [(s["chainage_m"], s["center_x"], s["center_y"]) for s in _STATIONS]
assert len(terrain) == len(plan_chunks(stations)) == len(plan_chunks(stations, 1200))
assert len(big) == len(plan_chunks(stations, 600)) > len(terrain)
assert big[-1]["label"].endswith(f"{len(big)}장")
def _texts(drawing: dict) -> str:
return json.dumps(drawing["entities"], ensure_ascii=False)
def _frame_labels(drawing: dict) -> set[str]:
return {
(e.get("shapeData") or {}).get("label")
for e in drawing["entities"]
if e.get("layerId") == "b08-frame" and e["type"] == "Text"
}
def _segment(entity: dict) -> tuple[float, float, float]:
"""두 점 PolyLine(자식 Line 하나) — (길이, 가운데 x, 가운데 y) 종이 mm."""
(child,) = entity["children"]
a, b = child["shapeData"]["startPoint"], child["shapeData"]["endPoint"]
return (
math.hypot(b["x"] - a["x"], b["y"] - a["y"]),
(a["x"] + b["x"]) / 2.0,
(a["y"] + b["y"]) / 2.0,
)
def test_drawing_scale_text_frame_and_paper_lengths() -> None:
box = (0.0, 0.0, 400.0, 300.0)
pieces = {
"station": {
"items": [
{"x": 100.0, "y": 100.0, "nx": 1.0, "ny": 0.0, "label": "NO.5", "major": True}
]
},
"endpoints": {"items": [{"label": "시점", "x": 50.0, "y": 50.0, "nx": 0.0, "ny": 1.0}]},
}
base = build_plan_kind_drawing("plan_x", "시험", box, pieces)
assert build_plan_kind_drawing("plan_x", "시험", box, pieces, 1200) == base
small = build_plan_kind_drawing("plan_x", "시험", box, pieces, 600)
assert "S = 1/1,200" in _texts(base) and "S = 1/600" in _texts(small)
assert "S = 1/1,200" not in _texts(small)
scales = [
e
for e in small["entities"]
if str((e.get("shapeData") or {}).get("label")).startswith("S = ")
]
assert [e["shapeData"]["label"] for e in scales] == ["S = 1/600"]
# 도곽 축척 칸 — A1 분모 600 · A3 1200 (1/1,200 그림은 1200 · 2400)
assert {"600", "1200"} <= _frame_labels(small) and "2400" not in _frame_labels(small)
assert {"1200", "2400"} <= _frame_labels(base) and "600" not in _frame_labels(base)
# 종이 mm 기호는 축척과 무관하게 같은 길이 · 실거리 자리는 축척만큼 멀어짐
for piece, paper_mm in (("station", 3.0), ("endpoints", 12.0)):
lines = [
[
e
for e in d["entities"]
if e.get("layerId") == layer_id(piece) and e["type"] != "Text"
]
for d in (base, small)
]
(length, x, y), (length_s, x_s, y_s) = _segment(lines[0][0]), _segment(lines[1][0])
assert length == pytest.approx(paper_mm) and length_s == pytest.approx(paper_mm)
assert (x_s, y_s) == (pytest.approx(2 * x), pytest.approx(2 * y)) # 실거리 자리는 2배
# test_b07_plan_kinds 의 IP 표 시험과 같은 노선 · 곡선
_ROUTE = [(0.0, 0.0, 0.0), (85.0, 85.0, 0.0), (115.0, 100.0, 15.0), (200.0, 100.0, 100.0)]
_CURVES = [
{"apex": [500.0, 500.0], "start": [490.0, 500.0], "end": [500.0, 510.0], "radius_m": 10.0},
{"apex": [100.0, 0.0], "start": [85.0, 0.0], "end": [100.0, 15.0], "radius_m": 15.0},
]
def _gap(mm: float) -> tuple[float, int]:
"""y = 0 등고선에서 잘려 나간 길이(m) · 그 줄을 덮는 표 수."""
box = (-20.0, -20.0, 300.0, 200.0)
pieces = {"contour": {"lines": [[[-20.0, 0.0], [300.0, 0.0]]]}}
table = ip_table_piece(
pieces, _CURVES, _ROUTE, box, {"number": 1, "start_m": 0.0, "end_m": 200.0}, 1, mm
)
kept = sum(line[-1][0] - line[0][0] for line in pieces["contour"]["lines"])
covering = sum(
1
for b in table["blocks"]
if box[1] + (b["origin"][1] - b["rows"] * _ROW_H) / mm < 0.0 < box[1] + b["origin"][1] / mm
)
return 320.0 - kept, covering
def test_ip_table_clip_and_parcel_label_follow_scale() -> None:
assert _gap(1000.0 / 1200) == (pytest.approx(43.2), 1) # 지금 그림(test_b07_plan_kinds)과 같음
# 표 종이 폭 36 mm 그대로 → 1/600 실거리 21.6 m 씩(덮는 표마다)
removed, covering = _gap(1000.0 / 600)
assert covering >= 1 and removed == pytest.approx(sum(_COLUMNS) * 0.6 * covering)
items = [{"lines": ["1-1", "임야"], "center": [0.0, 0.0]}]
(big,) = label_rects(items)
(half,) = label_rects(items, 1000.0 / 600)
assert half[2] - half[0] == pytest.approx((big[2] - big[0]) / 2)
assert half[3] - half[1] == pytest.approx((big[3] - big[1]) / 2)
def test_실거리_상수는_종이_거리가_같게_141_2() -> None:
"""비탈 눈금 · 대피소 띄움 · 필지 버퍼 = 1/1,200 실거리 × 축척 비 — 1/6,000 눈금 간격 10 m."""
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanSlope import slope_pieces, tick_step_m
assert tick_step_m(1200) == 2.0 and tick_step_m(6000) == 10.0 and tick_step_m(500) < 1.0
route = [(0.0, 0.0, 0.0), (200.0, 200.0, 0.0)]
records = [
{"chainage_m": c, "left_edge_m": 2.0, "left_end_m": 6.0, "right_edge_m": -2.0,
"right_end_m": -6.0}
for c in (0.0, 200.0)
] # fmt: skip
for scale in (1200, 6000):
ticks = slope_pieces(route, records, 0.0, 200.0, tick_step_m(scale))[3]["ticks"]
left = sorted(t[0][0] for t in ticks if t[0][1] > 0)
gaps_mm = [(b - a) * 1000.0 / scale for a, b in zip(left, left[1:])]
assert min(gaps_mm) == pytest.approx(2000.0 / 1200.0) # 1.67 mm — 검은 띠 없음