Files
Aislo/resources/tester/test_b07_long_sheet.py
T
eomsangdonandClaude Opus 5.5 df0e64ffe2 feat(B07): 종단도 장마다 우측 상단 평면 위치도 · 종단 ⚙ 「평면도」 척도 칸 (521-2 · 요구 340-2)
- 새 Engine_Cad_LongPlan — 작도 영역 우상단 5 mm 안쪽 150×100 mm 칸 · 자동 = 전체 노선 맞춤(분모 500 올림) · 척도 정함 = 이 장 중심 확대 · 밖 노선 흐린 파선 · 칸 밖 잘림 · 이 장 굵게 · 시 · 종점 측점 · 방위표 · 「평면도 S=1/n」 · 「n/N 장」 · 좌표 없으면 안 그림
- 장 나눔 — 그래프가 칸을 덮으면 측점 줄여 장 더 나눔 · 장 목록에 number · total
- 축척 — longitudinal.plan 기본 자동 · 범위 1/500~1/50,000 · ⚙ 칸 = 서버 기본값 열쇠 그대로(셋째 칸 + 자동)
- 칸 앞 확정 종단 저장본 — 목록 받을 때 종단만 한 번 모두 풂(DRAWING_FORMAT 그대로)
- 시험 — 칸 자리 · 안 겹침 · 장마다 강조 · 척도 확대 · 좌표 없음 · 한 번 풂 · plan 범위 · ⚙ 평면 칸

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 08:01:02 +09:00

200 lines
8.3 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 종단도 — 장마다 그림 좌하단 = 작도영역 좌하단 · 고정 축척 · 넘치면 장을 더 나눔 (59-3).
우측 상단 평면 위치도(521-2) — 칸 자리 · 그래프와 안 겹침 · 장마다 강조 구간 · 척도.
"""
import json
import math
import pytest
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import FRAME_LAYER_ID
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Long import (
build_longitudinal_drawing,
longitudinal_chunks,
)
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_LongPlan import (
LONG_PLAN_LAYER_ID,
plan_box_overlaps,
)
from B07_DesignDetail.B07_DesignDetail_Engine_Template import (
drawing_scales_path,
entities_bbox,
usable_area,
use_project_templates,
)
from B07_DesignDetail.B07_DesignDetail_Router_Support_Io import (
_read_manifest,
_write_manifest,
release_old_long_sheets,
)
def _longitudinal(count: int, interval: float, slope: float) -> dict:
xs = [index * interval for index in range(count)]
return {
"samples": [{"valid": True, "chainage_m": x, "elevation_m": 100.0 + slope * x} for x in xs],
"stations": [{"chainage_m": x} for x in xs],
"design_profiles": [
{"samples": [{"chainage_m": x, "elevation_m": 100.5 + slope * x} for x in xs]}
],
}
def _sheets(longitudinal: dict) -> list[tuple[tuple, tuple]]:
result = []
for chunk in longitudinal_chunks(longitudinal):
entities = build_longitudinal_drawing(longitudinal, chunk["id"], chunk)["entities"]
content = entities_bbox([e for e in entities if e.get("layerId") != FRAME_LAYER_ID])
frame = entities_bbox([e for e in entities if e.get("layerId") == FRAME_LAYER_ID])
result.append((content, frame))
return result
@pytest.mark.parametrize(
("count", "interval", "slope"),
[(66, 20.0, 0.05), (40, 40.0, 0.0), (40, 20.0, 0.25)],
)
def test_every_sheet_fits_and_starts_at_same_corner(count, interval, slope) -> None:
longitudinal = _longitudinal(count, interval, slope)
sheets = _sheets(longitudinal)
usable_w, usable_h = usable_area()
corners = set()
for content, frame in sheets:
assert content[2] - content[0] <= usable_w + 1e-6
assert content[3] - content[1] <= usable_h + 1e-6
corners.add((round(content[0] - frame[0], 3), round(content[1] - frame[1], 3)))
assert len(corners) == 1
chunks = longitudinal_chunks(longitudinal)
# 이어지는 장은 경계 측점 하나를 나눠 갖고 끝까지 덮는다.
assert chunks[0]["start_m"] == 0.0
assert chunks[-1]["end_m"] == (count - 1) * interval
for before, after in zip(chunks, chunks[1:]):
assert before["end_m"] == after["start_m"]
def test_wide_interval_splits_more() -> None:
# 40 m 간격 30측점 = 1,160 mm > 작도영역 폭 → 30개보다 적게 담는다
chunks = longitudinal_chunks(_longitudinal(40, 40.0, 0.0))
assert len(chunks) > 2
# ── 우측 상단 평면 위치도(521-2 · 요구 340-2) ─────────────────────────────────
def _with_xy(longitudinal: dict) -> dict:
for station in longitudinal["stations"]:
chainage = station["chainage_m"]
station["center_x"] = 200000.0 + chainage
station["center_y"] = 500000.0 + 60.0 * math.sin(chainage / 150.0)
return longitudinal
def _plan_sheets(longitudinal: dict) -> list[dict]:
"""장마다 그래프 bbox · 칸 엔티티 · 굵은(이 장) 선 꼭짓점 · 글."""
sheets = []
for chunk in longitudinal_chunks(longitudinal):
entities = build_longitudinal_drawing(longitudinal, chunk["id"], chunk)["entities"]
graph = [e for e in entities if e["layerId"] not in (FRAME_LAYER_ID, LONG_PLAN_LAYER_ID)]
plan = [e for e in entities if e["layerId"] == LONG_PLAN_LAYER_ID]
bold = [
(child["shapeData"][key]["x"], child["shapeData"][key]["y"])
for entity in plan
for child in entity.get("children") or []
if child["lineWidth"] == 3
for key in ("startPoint", "endPoint")
]
texts = [e["shapeData"]["label"] for e in plan if e["type"] == "Text"]
sheets.append(
{
"graph": graph,
"bbox": entities_bbox(graph),
"plan": plan,
"bold": bold,
"texts": texts,
}
)
return sheets
def test_평면_칸_작도영역_우상단_안_겹침_장마다_강조() -> None:
longitudinal = _with_xy(_longitudinal(66, 20.0, 0.05))
sheets = _plan_sheets(longitudinal)
usable_w, usable_h = usable_area()
lows = []
for number, sheet in enumerate(sheets, start=1):
x0, y0 = sheet["bbox"][0], sheet["bbox"][1]
box = entities_bbox(sheet["plan"])
# 칸 = 작도 영역 우상단 5 mm 안쪽 150×100 mm
assert box == pytest.approx(
(x0 + usable_w - 155.0, y0 + usable_h - 105.0, x0 + usable_w - 5.0, y0 + usable_h - 5.0)
)
assert not plan_box_overlaps(longitudinal, sheet["graph"], sheet["bbox"])
assert f"{number}/{len(sheets)} 장" in sheet["texts"]
assert any(text.startswith("평면도 S=1/") for text in sheet["texts"])
assert sheet["bold"] # 이 장 구간 굵게
lows.append(min(x for x, _y in sheet["bold"]) - box[0])
# 자동 = 모든 장이 같은 축척 · 같은 자리 — 굵은 구간만 장마다 오른쪽으로 옮겨 감
assert lows == sorted(lows) and len(set(lows)) == len(lows)
def test_칸을_덮으면_측점_줄여_장_더_나눔() -> None:
# 25 m 간격 30 측점 = 725 mm(폭 안) · 오르막 그래프가 우상단 칸을 덮음
plain = longitudinal_chunks(_longitudinal(60, 25.0, 0.1))
with_box = _with_xy(_longitudinal(60, 25.0, 0.1))
assert longitudinal_chunks(with_box)[0]["end_m"] < plain[0]["end_m"]
for sheet in _plan_sheets(with_box):
assert not plan_box_overlaps(with_box, sheet["graph"], sheet["bbox"])
def test_좌표_없으면_칸_없음() -> None:
assert all(not sheet["plan"] for sheet in _plan_sheets(_longitudinal(40, 20.0, 0.0)))
def test_척도_정하면_이_장_확대_밖_흐린_파선(tmp_path) -> None:
longitudinal = _with_xy(_longitudinal(66, 20.0, 0.05))
use_project_templates(tmp_path)
try:
path = drawing_scales_path(tmp_path)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps({"longitudinal": {"plan": 3000}}), encoding="utf-8")
sheets = _plan_sheets(longitudinal)
finally:
use_project_templates(None)
for sheet in sheets:
assert "평면도 S=1/3,000" in sheet["texts"]
assert any(entity.get("lineDash") for entity in sheet["plan"]) # 밖 노선 파선
box = entities_bbox(sheet["plan"])
xs = [x for x, _y in sheet["bold"]]
# 이 장 중심 = 칸 노선 자리 가운데
assert (min(xs) + max(xs)) / 2.0 == pytest.approx((box[0] + box[2]) / 2.0, abs=1.0)
def test_칸_앞_확정_종단만_한_번_풂(tmp_path) -> None:
drawings = tmp_path / "B07_DesignDetail" / "drawings"
drawings.mkdir(parents=True)
old = {"kind": "longitudinal", "confirmed": True, "file": "x"}
_write_manifest(
tmp_path,
{
"drawings": {
"longitudinal_1": dict(old),
"longitudinal_2": dict(old),
"cross_00020m": {"kind": "cross", "confirmed": True, "file": "x"},
}
},
)
for name in ("longitudinal_1", "longitudinal_2", "cross_00020m"):
(drawings / f"{name}.json").write_text('{"layers": []}', encoding="utf-8")
release_old_long_sheets(tmp_path)
after = _read_manifest(tmp_path)["drawings"]
assert not after["longitudinal_1"]["confirmed"] and not after["longitudinal_2"]["confirmed"]
assert after["cross_00020m"]["confirmed"]
assert not (drawings / "longitudinal_1.json").exists() # 옛 저장본 걷힘
# 새 확정본(칸 층 있음)은 그대로
after["longitudinal_1"] = dict(old)
_write_manifest(tmp_path, {"drawings": after})
(drawings / "longitudinal_1.json").write_text(
json.dumps({"layers": [{"id": LONG_PLAN_LAYER_ID}]}), encoding="utf-8"
)
release_old_long_sheets(tmp_path)
assert _read_manifest(tmp_path)["drawings"]["longitudinal_1"]["confirmed"]