- 축척 = 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
194 lines
8.9 KiB
Python
194 lines
8.9 KiB
Python
"""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 — 검은 띠 없음
|