- 표 아래선 = 칸 아래 + 5 mm · 같은 행 표 윗선 · 아랫선 한 높이 · 표 가운데 x = 칸 가운데 - [상세] 글 표 바로 위 · 그림 아래 끝 = 글(없으면 표) 위 8 mm · 도로 중심(offset 0) = 칸 가운데 - 넘침 = 블록 틀이 칸 위 · 옆을 넘을 때 붉은 칸 틀(축척 그대로) - 배치 판 5 — 옛 판 확정 장 안내 글 새 배치로 - test_140 칸 가운데 판정을 새 배치로 · test_174 새 시험 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL
151 lines
6.7 KiB
Python
151 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""140-3 B07 횡단면도 장 배치 — 장마다 열 × 행 칸(기본 4 × 4).
|
||
|
||
못박는 것
|
||
- 칸 = 그림칸(usable_area)을 똑같이 나눈 자리 · 차례 = 좌하단 → 위 → 오른쪽 열.
|
||
- 측점 73개 · 4 × 4 → 16 · 16 · 16 · 16 · 9 · 17번째 측점 = 2장 첫 칸.
|
||
- 장마다 다른 배치(sheets {장 번호: [열, 행]}) · 기본 바꾸기.
|
||
- 단면 블록 = 칸 아래 가운데(174-1) · 블록 크기 = 1/100 그대로(section_block_size).
|
||
- 도각 = 고정 그림칸 — 내용이 작아도 커도 같은 자리.
|
||
- 배치 저장 · 읽기(깨진 값 거절) · 담긴 측점이 바뀐 확정 장만 풂.
|
||
"""
|
||
|
||
import json
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import FRAME_LAYER_ID
|
||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Sheet import (
|
||
CELL_TABLE_PAD_MM,
|
||
build_cross_sheet,
|
||
grid_slots,
|
||
plan_cross_sheets,
|
||
section_block_size,
|
||
)
|
||
from B07_DesignDetail.B07_DesignDetail_Engine_Template import entities_bbox, usable_area
|
||
from B07_DesignDetail.B07_DesignDetail_Router_Support import (
|
||
_cross_sheet_plan,
|
||
read_cross_layout,
|
||
write_cross_layout,
|
||
)
|
||
from B07_DesignDetail.B07_DesignDetail_Router_Support_Io import _read_manifest, _write_manifest
|
||
|
||
|
||
def _source(chainage: float, slope: float, half: int = 10) -> dict:
|
||
return {
|
||
"chainage_m": chainage,
|
||
"samples": [
|
||
{"valid": True, "offset_m": float(x), "elevation_m": 100.0 + slope * x}
|
||
for x in range(-half, half + 1)
|
||
],
|
||
}
|
||
|
||
|
||
def test_grid_slots_bottom_left_up_then_right() -> None:
|
||
w, h = usable_area()
|
||
slots = grid_slots(6, 4, 4)
|
||
cw, ch = w / 4, h / 4
|
||
assert slots[0] == pytest.approx([-w / 2 + cw / 2, -h / 2 + ch / 2]) # 좌하단
|
||
assert slots[1] == pytest.approx([-w / 2 + cw / 2, -h / 2 + 1.5 * ch]) # 그 위
|
||
assert slots[4] == pytest.approx([-w / 2 + 1.5 * cw, -h / 2 + ch / 2]) # 다음 열 맨 아래
|
||
|
||
|
||
def test_73_stations_default_grid_17th_is_sheet_two() -> None:
|
||
chainages = [i * 20.0 for i in range(73)]
|
||
sheets = plan_cross_sheets(chainages)
|
||
assert [len(s["chainages"]) for s in sheets] == [16, 16, 16, 16, 9]
|
||
assert sheets[1]["chainages"][0] == chainages[16] # 17번째 측점 = 2장 첫 칸
|
||
assert sheets[1]["number"] == 2 and sheets[1]["grid"] == [4, 4]
|
||
assert sheets[1]["id"] == "cross_s00320m" # 장 id = 첫 측점
|
||
|
||
|
||
def test_per_sheet_and_default_layout() -> None:
|
||
chainages = [float(i) for i in range(40)]
|
||
layout = {"default": [3, 3], "sheets": {"1": [2, 2], "3": [5, 4]}}
|
||
sheets = plan_cross_sheets(chainages, layout)
|
||
assert [s["grid"] for s in sheets] == [[2, 2], [3, 3], [5, 4], [3, 3]]
|
||
assert [len(s["chainages"]) for s in sheets] == [4, 9, 20, 7]
|
||
|
||
|
||
def _frame_bbox(drawing: dict) -> tuple:
|
||
return entities_bbox([e for e in drawing["entities"] if e.get("layerId") == FRAME_LAYER_ID])
|
||
|
||
|
||
def test_block_centered_in_cell_at_scale_and_frame_fixed() -> None:
|
||
table = {"ground": 100.0, "planned": 100.0}
|
||
small = [_source(0.0, 0.05, 6)]
|
||
big = [_source(0.0, 0.6, 15), _source(20.0, 0.2, 8)]
|
||
drawings = []
|
||
for sources in (small, big):
|
||
sheet = plan_cross_sheets([s["chainage_m"] for s in sources])[0]
|
||
sections = [
|
||
{"chainage": s["chainage_m"], "source": s, "quantity_table": table, "title": ""}
|
||
for s in sources
|
||
]
|
||
drawing = build_cross_sheet(sheet, sections)
|
||
for source, slot, placement in zip(
|
||
sources, sheet["slots"], drawing["cross_placements"], strict=True
|
||
):
|
||
left, bottom, right, top = placement["frame"]
|
||
# 좌우 대칭 단면 — 블록 가운데 x = 칸 가운데 · 아래 = 칸 아래 + 표 여백(174-1)
|
||
assert (left + right) / 2 == pytest.approx(slot[0])
|
||
cell_bottom = slot[1] - usable_area()[1] / 8
|
||
assert bottom == pytest.approx(cell_bottom + CELL_TABLE_PAD_MM - 1.0)
|
||
# 1/100 그대로 — 블록 크기가 자기 치수 그대로(칸 맞춤으로 줄이지 않음)
|
||
width, height = section_block_size(source, None, design=None)
|
||
assert right - left == pytest.approx(width, abs=0.5)
|
||
drawings.append(drawing)
|
||
# 도각 = 고정 그림칸 — 내용 크기와 무관하게 같은 자리
|
||
assert _frame_bbox(drawings[0]) == pytest.approx(_frame_bbox(drawings[1]))
|
||
|
||
|
||
def _project(tmp_path: Path, count: int) -> tuple[Path, Path]:
|
||
root = tmp_path / "project"
|
||
cross_dir = root / "B06_Section" / "cross_sections"
|
||
cross_dir.mkdir(parents=True)
|
||
longitudinal_path = root / "B06_Section" / "longitudinal" / "longitudinal.json"
|
||
longitudinal_path.parent.mkdir(parents=True)
|
||
stations = []
|
||
for i in range(count):
|
||
chainage = i * 20
|
||
stations.append({"chainage_m": float(chainage), "center_x": 0.0, "center_y": 0.0})
|
||
(cross_dir / f"cross_{chainage:05d}m.json").write_text(
|
||
json.dumps(_source(float(chainage), 0.1)), "utf-8"
|
||
)
|
||
longitudinal_path.write_text(json.dumps({"stations": stations}), "utf-8")
|
||
return root, longitudinal_path
|
||
|
||
|
||
def test_layout_store_and_release_changed_confirmed(tmp_path: Path) -> None:
|
||
root, longitudinal_path = _project(tmp_path, 20)
|
||
assert read_cross_layout(root) == {"default": [4, 4], "sheets": {}} # 파일 없으면 기본
|
||
ids = [s["id"] for s in _cross_sheet_plan(root, longitudinal_path)]
|
||
assert ids == ["cross_s00000m", "cross_s00320m"] # 16 + 4
|
||
manifest = _read_manifest(root)
|
||
manifest["drawings"] = {i: {"confirmed": True} for i in ids}
|
||
_write_manifest(root, manifest)
|
||
|
||
# 2장만 3 × 3 — 1장 측점 그대로(확정 유지) · 2장 측점 그대로(4개 < 9칸) → 풀 장 없음
|
||
assert (
|
||
write_cross_layout(root, longitudinal_path, {"default": [4, 4], "sheets": {"2": [3, 3]}})
|
||
== []
|
||
)
|
||
assert read_cross_layout(root)["sheets"] == {"2": [3, 3]}
|
||
|
||
# 1장 3 × 3 — 1장 9개로 줄고 2장은 다른 측점에서 시작 → 1장 풂(옛 측점 16개 돌려줌)
|
||
stale = write_cross_layout(
|
||
root, longitudinal_path, {"default": [4, 4], "sheets": {"1": [3, 3]}}
|
||
)
|
||
assert stale[:16] == [i * 20 for i in range(16)]
|
||
drawings = _read_manifest(root)["drawings"]
|
||
assert drawings["cross_s00000m"]["confirmed"] is False
|
||
assert drawings["cross_s00320m"]["confirmed"] is False # 사라진 장 id 도 풂
|
||
new_ids = [s["id"] for s in _cross_sheet_plan(root, longitudinal_path)]
|
||
assert new_ids == ["cross_s00000m", "cross_s00180m"]
|
||
|
||
with pytest.raises(ValueError):
|
||
write_cross_layout(root, longitudinal_path, {"default": [0, 4], "sheets": {}})
|
||
with pytest.raises(ValueError):
|
||
write_cross_layout(root, longitudinal_path, {"default": [4, 4], "sheets": {"x": [2, 2]}})
|