- 쌓기 · 최소 장수 함수 지움 · grid_slots(n, 열, 행) 칸 가운데(좌하단 → 위 → 오른쪽) · 1/100 그대로
- 저장 B07_DesignDetail/cross_layout.json {default [4,4], sheets {장 번호: [열, 행]}} · GET · PUT cross-layout
- 배치 바꾸면 담긴 측점이 바뀐 확정 장 풂 · 그 측점 설계 잠정 · CROSS_SHEET_LAYOUT = 3
- 장 나눔이 설계 · 덧그림과 무관 — 목록 · 확정 · 수정에서 덧그림 재기 빠짐
- 좌측 정보 칸 「배치 열 × 행 [적용] · [모든 장 기본]」 → 다시 세워 같은 장 번호 엶
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL
149 lines
6.5 KiB
Python
149 lines
6.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""140-3 B07 횡단면도 장 배치 — 장마다 열 × 행 칸(기본 4 × 4).
|
||
|
||
못박는 것
|
||
- 칸 = 그림칸(usable_area)을 똑같이 나눈 자리 · 차례 = 좌하단 → 위 → 오른쪽 열.
|
||
- 측점 73개 · 4 × 4 → 16 · 16 · 16 · 16 · 9 · 17번째 측점 = 2장 첫 칸.
|
||
- 장마다 다른 배치(sheets {장 번호: [열, 행]}) · 기본 바꾸기.
|
||
- 단면 블록 가운데 = 칸 가운데 · 블록 크기 = 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 (
|
||
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"]
|
||
# 블록 가운데 = 칸 가운데
|
||
assert (left + right) / 2 == pytest.approx(slot[0])
|
||
assert (bottom + top) / 2 == pytest.approx(slot[1])
|
||
# 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]}})
|