- 장마다 칸 격자선(세로 열 + 1 · 가로 행 + 1 · 도각 층 · 인쇄 · DXF) - 단면은 블록 틀 가운데 = 칸 가운데 그대로 · 장 안 블록 테두리 안 그림(측점 한 장은 그대로) - 칸보다 큰 단면 = overflow_cells → CAD 가이드 붉은 틀 + 화면 알림(축척 그대로) - 배치 판 4 — 옛 판 확정 장은 「옛 배치」 안내 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL
131 lines
5.7 KiB
Python
131 lines
5.7 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""169 도면 그림 영역 지정 · 횡단 격자 표.
|
||
|
||
못박는 것
|
||
- 169-1 그림칸 = 작도 영역 그대로(여백 없음) · 도각 편집이 영역을 싣고 · 저장(어긋나면 옛 값).
|
||
- 169-1 도면 좌표 가이드 영역 = 도각 bbox ↔ 양식 bbox 로 옮긴 영역 · 도각 아닌 것(블록 틀)이면 None.
|
||
- 169-2 횡단 장 = 칸 격자선(열 + 1 · 행 + 1 · 칸 경계) · 장 안 블록 테두리 없음 · 큰 단면 → 넘친 칸.
|
||
- 169-4 구조물 도각(외곽 좌하단 0,0) 응답에 영역.
|
||
"""
|
||
|
||
import json
|
||
from pathlib import Path
|
||
from uuid import uuid5
|
||
|
||
import pytest
|
||
|
||
from B07_DesignDetail import B07_DesignDetail_Engine_Template as engine
|
||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
|
||
_POLY_NS,
|
||
FRAME_LAYER_ID,
|
||
build_cross_drawing,
|
||
)
|
||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Sheet import build_cross_sheet, plan_cross_sheets
|
||
from B07_DesignDetail.B07_DesignDetail_Router import _drawing_guide
|
||
from M02_MasterTemplete.M02_MasterTemplete_Router_Drawing import structure_frame
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
TABLE = {"ground": 100.0, "planned": 100.0}
|
||
|
||
|
||
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 _sheet(sources: list[dict], layout: dict | None = None) -> tuple[dict, dict]:
|
||
sheet = plan_cross_sheets([s["chainage_m"] for s in sources], layout)[0]
|
||
sections = [
|
||
{"chainage": s["chainage_m"], "source": s, "quantity_table": TABLE, "title": ""}
|
||
for s in sources
|
||
]
|
||
return sheet, build_cross_sheet(sheet, sections)
|
||
|
||
|
||
def test_usable_area_is_drawing_area_without_margin():
|
||
engine.use_project_templates(None)
|
||
x0, y0, x1, y1 = engine.drawing_area()
|
||
assert engine.usable_area() == (x1 - x0, y1 - y0) == (770.0, 520.0)
|
||
|
||
|
||
def test_frame_edit_loads_and_saves_area(tmp_path):
|
||
engine.use_project_templates(None)
|
||
assert engine.frame_template_document()["drawing_area"] == [42.0, 47.0, 812.0, 567.0]
|
||
path = engine.save_project_template(tmp_path, [], area=[10, 20, 410, 320])
|
||
assert json.loads(path.read_text(encoding="utf-8"))["drawing_area"] == [10, 20, 410, 320]
|
||
# 어긋난 값(뒤집힘 · 글자) → 고치기 전 값을 이어 받음
|
||
for bad in ([410, 20, 10, 320], ["a", 0, 1, 1], None):
|
||
path = engine.save_project_template(tmp_path, [], area=bad)
|
||
assert json.loads(path.read_text(encoding="utf-8"))["drawing_area"] == [10, 20, 410, 320]
|
||
# 라우터가 요청 도면의 영역을 넘김
|
||
router = (ROOT / "B07_DesignDetail/B07_DesignDetail_Router_Frame.py").read_text(
|
||
encoding="utf-8"
|
||
)
|
||
assert 'request.drawing.get("drawing_area")' in router and "area=area" in router
|
||
|
||
|
||
def test_cross_sheet_grid_lines_no_block_border_and_guide_area():
|
||
engine.use_project_templates(None)
|
||
w, h = engine.usable_area()
|
||
sources = [_source(i * 20.0, 0.05, 6) for i in range(3)]
|
||
sheet, drawing = _sheet(sources, {"default": [3, 2]})
|
||
by_id = {e["id"]: e for e in drawing["entities"]}
|
||
|
||
def line(name: str) -> tuple[dict, dict]:
|
||
entity = by_id[str(uuid5(_POLY_NS, f"{sheet['id']}:{FRAME_LAYER_ID}:grid-{name}"))]
|
||
assert entity["layerId"] == FRAME_LAYER_ID
|
||
shape = entity["children"][0]["shapeData"]
|
||
return shape["startPoint"], shape["endPoint"]
|
||
|
||
# 격자선 = 세로 열 + 1 · 가로 행 + 1 · 칸 경계 자리 · 그림칸 끝에서 끝까지
|
||
for i in range(3 + 1):
|
||
a, b = line(f"v{i}")
|
||
assert a["x"] == b["x"] == pytest.approx(-w / 2 + w * i / 3)
|
||
assert (a["y"], b["y"]) == pytest.approx((-h / 2, h / 2))
|
||
for j in range(2 + 1):
|
||
a, b = line(f"h{j}")
|
||
assert a["y"] == b["y"] == pytest.approx(-h / 2 + h * j / 2)
|
||
assert (a["x"], b["x"]) == pytest.approx((-w / 2, w / 2))
|
||
assert str(uuid5(_POLY_NS, f"{sheet['id']}:{FRAME_LAYER_ID}:grid-v4")) not in by_id
|
||
# 장 안 블록 테두리 없음(측점 한 장 테두리와 같은 id 자리)
|
||
for source in sources:
|
||
seed = f"{sheet['id']}:{source['chainage_m']}:{FRAME_LAYER_ID}"
|
||
assert str(uuid5(_POLY_NS, seed)) not in by_id
|
||
# 가이드 영역(도면 좌표) = 그림칸 그대로 · 넘침 없음
|
||
guide = _drawing_guide(drawing)
|
||
assert guide["area"] == pytest.approx([-w / 2, -h / 2, w / 2, h / 2], abs=1e-3)
|
||
assert guide["marks"] == [] and drawing["overflow_cells"] == []
|
||
|
||
|
||
def test_big_section_marks_its_cell():
|
||
engine.use_project_templates(None)
|
||
w, h = engine.usable_area()
|
||
# 4 × 4 칸(192.5 × 130 mm)에 60 m 폭 단면(600 mm) — 첫 칸(좌하단)만 넘침
|
||
sources = [_source(0.0, 0.05, 30), _source(20.0, 0.05, 6)]
|
||
_sheet_, drawing = _sheet(sources, {"default": [4, 4]})
|
||
cw, ch = w / 4, h / 4
|
||
assert drawing["overflow_cells"] == [pytest.approx([-w / 2, -h / 2, -w / 2 + cw, -h / 2 + ch])]
|
||
assert _drawing_guide(drawing)["marks"] == drawing["overflow_cells"]
|
||
|
||
|
||
def test_station_drawing_border_is_not_a_frame():
|
||
# 측점 한 장 — 도각 층에 블록 테두리만 있음 → 가이드 없음(틀린 선 대신)
|
||
drawing = build_cross_drawing(_source(0.0, 0.1), "cross_00000m", None, None, TABLE, "")
|
||
assert any(e.get("layerId") == FRAME_LAYER_ID for e in drawing["entities"])
|
||
assert _drawing_guide(drawing) is None
|
||
|
||
|
||
def test_structure_frame_area_in_frame_coords():
|
||
engine.use_project_templates(None)
|
||
frame = structure_frame()
|
||
min_x, min_y, *_ = engine.entities_bbox(engine.template_entities())
|
||
x0, y0, x1, y1 = engine.drawing_area()
|
||
assert frame["area"] == pytest.approx(
|
||
[x0 - min_x, y0 - min_y, x1 - min_x, y1 - min_y], abs=1e-3
|
||
)
|