작성 척도(횡단 1/100)를 도면 좌표에 넣고, A1 한 장에 들어가는 만큼 단면을 담는다. - 횡단 좌표를 종이 mm로(x10). 수량표도 종이 치수로 재조정(폭 108mm, 행 5mm) - 단면 폭 = 설계선이 원지반에서 갈라졌다 만나는 구간 + 여유 1.5m. 지표선도 그 범위로 자른다. 실측 갈라짐 폭 4.0~15.0m (종전 전폭 40m) - Engine_Cad_Sheet 신설: 칸 크기는 최대 블록 기준, 좌하단부터 아래→위, 그다음 오른쪽 열로 채우고 넘치면 다음 장. 23측점 -> 4장(3열x2행) - 목록·확정을 장 단위로. 확정 시 담긴 측점 전부의 B06 단면적을 재계산하고 수량표는 측점별로 매니페스트에 보관 - 700줄 제한: 수량표를 Engine_Cad_Table로, 라우터 동기 헬퍼를 Router_Support로 분리 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
180 lines
6.6 KiB
Python
180 lines
6.6 KiB
Python
"""B07 횡단면도 장 배치 — A1 한 장에 들어가는 만큼 단면을 담는다.
|
|
|
|
채우는 순서는 **좌하단부터 아래에서 위로, 그다음 오른쪽 열**이다
|
|
(2026-08-29 사용자 확정). 한 장에 몇 개가 들어가는지는 단면 블록의 크기가
|
|
정하며, 블록 크기는 작성 척도(1/100)와 "설계선이 원지반과 갈라지는 구간"이
|
|
정한다 — 즉 종이가 허용하는 만큼만 담고 남으면 다음 장으로 넘긴다.
|
|
"""
|
|
|
|
import re
|
|
from typing import Any
|
|
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
|
|
CROSS_MM,
|
|
CROSS_TABLE_LAYER_ID,
|
|
DESIGN_LAYER_ID,
|
|
DRAWING_FORMAT,
|
|
FRAME_LAYER_ID,
|
|
GROUND_LAYER_ID,
|
|
ROCK_LAYER_ID,
|
|
STRUCTURE_LAYER_ID,
|
|
_clip_polyline,
|
|
_layer,
|
|
_section_window,
|
|
build_cross_drawing,
|
|
points_from_samples,
|
|
)
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Table import (
|
|
cross_table_height,
|
|
cross_table_width,
|
|
)
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Template import (
|
|
entities_bbox,
|
|
frame_entities,
|
|
usable_area,
|
|
)
|
|
|
|
# 장 도면 id — 측점 도면(cross_00020m)과 겹치지 않는 형식.
|
|
CROSS_SHEET_ID = re.compile(r"cross_s(\d{2,})")
|
|
|
|
# 블록 사이 여백(mm)과 테두리 여유 — build_cross_drawing의 테두리 값과 맞춘다.
|
|
_BLOCK_GAP_MM = 8.0
|
|
_BORDER_PAD_MM = 4.0
|
|
|
|
|
|
def _design_points(design_line: list[Any] | None) -> list[tuple[float, float]]:
|
|
points: list[tuple[float, float]] = []
|
|
for point in design_line if isinstance(design_line, list) else []:
|
|
if not isinstance(point, dict):
|
|
continue
|
|
x = point.get("offset_m")
|
|
y = point.get("elevation_m")
|
|
if isinstance(x, (int, float)) and isinstance(y, (int, float)):
|
|
points.append((float(x), float(y)))
|
|
return points
|
|
|
|
|
|
def section_block_size(
|
|
source: dict[str, Any], design_line: list[Any] | None, with_table: bool = True
|
|
) -> tuple[float, float]:
|
|
"""단면 블록 하나의 종이 크기(mm) — 엔티티를 만들지 않고 치수만 잰다.
|
|
|
|
build_cross_drawing의 테두리 계산과 같은 규칙을 쓴다: 가로는 그리는 범위와
|
|
수량표 중 넓은 쪽, 세로는 단면 높이 + 수량표.
|
|
"""
|
|
ground = points_from_samples(source.get("samples", []), "offset_m")
|
|
design = _design_points(design_line)
|
|
x0, x1 = _section_window(ground, design)
|
|
ground_clipped = _clip_polyline(ground, x0, x1)
|
|
ys = [y for _x, y in ground_clipped] + [y for x, y in design if x0 <= x <= x1]
|
|
half_height = ((max(ys) - min(ys)) / 2.0 if ys else 5.0) * CROSS_MM + 5.0
|
|
|
|
width = 2.0 * max(
|
|
(x1 - x0) / 2.0 * CROSS_MM + _BORDER_PAD_MM,
|
|
cross_table_width() / 2.0 + _BORDER_PAD_MM,
|
|
)
|
|
# 위: 단면 반높이 + 테두리 여유 / 아래: 반높이 + (표 간격 + 표) + 테두리 여유
|
|
top = half_height + _BORDER_PAD_MM
|
|
below = half_height + _BORDER_PAD_MM
|
|
if with_table:
|
|
below += 8.0 + cross_table_height()
|
|
return (width, top + below)
|
|
|
|
|
|
def plan_cross_sheets(blocks: list[tuple[int, float, float]]) -> list[dict[str, Any]]:
|
|
"""(측점, 폭, 높이) 목록을 A1 장으로 나눈다.
|
|
|
|
칸 크기는 전체 블록의 최대 폭·높이로 통일한다 — 장마다 칸이 달라지면
|
|
도면끼리 비교가 안 되기 때문이다. 한 장에 cols x rows개가 들어간다.
|
|
"""
|
|
if not blocks:
|
|
return []
|
|
usable_w, usable_h = usable_area()
|
|
cell_w = max(w for _c, w, _h in blocks) + _BLOCK_GAP_MM
|
|
cell_h = max(h for _c, _w, h in blocks) + _BLOCK_GAP_MM
|
|
cols = max(1, int(usable_w // cell_w))
|
|
rows = max(1, int(usable_h // cell_h))
|
|
per_sheet = cols * rows
|
|
|
|
sheets: list[dict[str, Any]] = []
|
|
for index in range(0, len(blocks), per_sheet):
|
|
group = blocks[index : index + per_sheet]
|
|
number = index // per_sheet + 1
|
|
sheets.append(
|
|
{
|
|
"id": f"cross_s{number:02d}",
|
|
"number": number,
|
|
"chainages": [chainage for chainage, _w, _h in group],
|
|
"cols": cols,
|
|
"rows": rows,
|
|
"cell": (cell_w, cell_h),
|
|
}
|
|
)
|
|
return sheets
|
|
|
|
|
|
def _cell_center(index: int, rows: int, cell: tuple[float, float]) -> tuple[float, float]:
|
|
"""좌하단부터 아래→위로 채우고, 열이 차면 오른쪽 열로 넘어간다."""
|
|
cell_w, cell_h = cell
|
|
usable_w, usable_h = usable_area()
|
|
column, row = divmod(index, rows)
|
|
x = -usable_w / 2.0 + cell_w * (column + 0.5)
|
|
y = -usable_h / 2.0 + cell_h * (row + 0.5)
|
|
return (x, y)
|
|
|
|
|
|
def build_cross_sheet(sheet: dict[str, Any], sections: list[dict[str, Any]]) -> dict[str, Any]:
|
|
"""한 장을 만든다. sections는 이 장에 담을 측점 입력들(배치 순서대로)."""
|
|
entities: list[dict[str, Any]] = []
|
|
rows = int(sheet.get("rows", 1))
|
|
cell = tuple(sheet.get("cell", (100.0, 100.0))) # type: ignore[arg-type]
|
|
|
|
for index, section in enumerate(sections):
|
|
seed_id = f"{sheet['id']}:{section['chainage']}"
|
|
# 1) 원점(0,0)에 한 번 만들어 블록이 원점 대비 어디에 놓이는지 잰다.
|
|
probe = build_cross_drawing(
|
|
section["source"],
|
|
seed_id,
|
|
section.get("design_line"),
|
|
section.get("design"),
|
|
section.get("quantity_table"),
|
|
section.get("title", ""),
|
|
)
|
|
bbox = entities_bbox(probe["entities"])
|
|
if bbox is None:
|
|
continue
|
|
min_x, min_y, max_x, max_y = bbox
|
|
center_x, center_y = _cell_center(index, rows, cell)
|
|
# 2) 칸 가운데에 오도록 원점을 옮겨 다시 만든다.
|
|
origin = (
|
|
center_x - (min_x + max_x) / 2.0,
|
|
center_y - (min_y + max_y) / 2.0,
|
|
)
|
|
placed = build_cross_drawing(
|
|
section["source"],
|
|
seed_id,
|
|
section.get("design_line"),
|
|
section.get("design"),
|
|
section.get("quantity_table"),
|
|
section.get("title", ""),
|
|
origin=origin,
|
|
)
|
|
entities.extend(placed["entities"])
|
|
|
|
bbox = entities_bbox(entities)
|
|
if bbox:
|
|
entities.extend(frame_entities(sheet["id"], bbox, fit=False))
|
|
|
|
return {
|
|
"format": DRAWING_FORMAT,
|
|
"entities": entities,
|
|
"layers": [
|
|
_layer(GROUND_LAYER_ID, "Existing Ground", locked=True),
|
|
_layer(DESIGN_LAYER_ID, "Design Plan"),
|
|
_layer(STRUCTURE_LAYER_ID, "Structure"),
|
|
_layer(ROCK_LAYER_ID, "Rock Boundary"),
|
|
_layer(CROSS_TABLE_LAYER_ID, "Quantity Table"),
|
|
_layer(FRAME_LAYER_ID, "Frame", locked=True),
|
|
],
|
|
}
|