feat(B07): 횡단면도 장 배치 장마다 열 × 행 칸 가운데 · 도각 고정 그림칸(140-3)
- 쌓기 · 최소 장수 함수 지움 · 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
This commit is contained in:
@@ -34,7 +34,6 @@ from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Sheet import (
|
||||
CROSS_SHEET_ID,
|
||||
build_cross_sheet,
|
||||
plan_cross_sheets,
|
||||
section_block_size,
|
||||
)
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Standard import (
|
||||
STANDARD_LABEL,
|
||||
@@ -82,6 +81,7 @@ from B07_DesignDetail.B07_DesignDetail_Router_Support_Plan import (
|
||||
from B07_DesignDetail.B07_DesignDetail_Schema import (
|
||||
DesignDrawingItem,
|
||||
)
|
||||
from common_util.common_util_json import atomic_write_json
|
||||
from common_util.common_util_route_profile import design_elevation_from_longitudinal
|
||||
from config.config_system import DRAWING_SCALE_PLAN
|
||||
|
||||
@@ -97,16 +97,67 @@ MASS_HAUL_ID = "mass_haul"
|
||||
COVER_ID = "cover"
|
||||
# 표준 횡단면도 — 노선 자료가 아니라 B06 표준 횡단면 설정값으로 그리는 한 장.
|
||||
CROSS_STANDARD_ID = "cross_standard"
|
||||
# 횡단 장 배치 판 — 단면마다 자기 그림 + 1 mm 박스(68-1). 이 판 전에 확정한 장은 옛 배치다.
|
||||
CROSS_SHEET_LAYOUT = 2
|
||||
# 횡단 장 배치 판 — 장마다 열 × 행 칸 가운데(140-3). 이 판 전에 확정한 장은 옛 배치다.
|
||||
CROSS_SHEET_LAYOUT = 3
|
||||
# 장마다 열 × 행 — {default: [열, 행], sheets: {장 번호: [열, 행]}}(140-3).
|
||||
CROSS_LAYOUT_FILE = "cross_layout.json"
|
||||
CROSS_GRID_DEFAULT = [4, 4]
|
||||
_CROSS_GRID_MAX = 10
|
||||
|
||||
|
||||
def _drawing_list(
|
||||
project_root: Path,
|
||||
longitudinal_path: Path,
|
||||
designs: dict[int | float, dict[str, Any]] | None = None,
|
||||
overlays: dict[int | float, tuple[float, float, float, float]] | None = None,
|
||||
) -> list[DesignDrawingItem]:
|
||||
def _cross_grid(value: Any) -> list[int]:
|
||||
if (
|
||||
not isinstance(value, (list, tuple))
|
||||
or len(value) != 2
|
||||
or not all(isinstance(n, int) and 1 <= n <= _CROSS_GRID_MAX for n in value)
|
||||
):
|
||||
raise ValueError(f"배치는 열 · 행 1~{_CROSS_GRID_MAX} 사이 정수로 적어 주세요.")
|
||||
return [int(value[0]), int(value[1])]
|
||||
|
||||
|
||||
def read_cross_layout(project_root: Path) -> dict[str, Any]:
|
||||
"""횡단 장 배치 — 파일이 없거나 깨졌으면 기본 4 × 4 만."""
|
||||
path = _design_root(project_root) / CROSS_LAYOUT_FILE
|
||||
try:
|
||||
return checked_cross_layout(_read_json(path))
|
||||
except (OSError, ValueError, AttributeError):
|
||||
return {"default": list(CROSS_GRID_DEFAULT), "sheets": {}}
|
||||
|
||||
|
||||
def checked_cross_layout(layout: dict[str, Any]) -> dict[str, Any]:
|
||||
sheets = layout.get("sheets") or {}
|
||||
if not all(str(key).isdigit() and int(key) >= 1 for key in sheets):
|
||||
raise ValueError("장 번호가 올바르지 않습니다.")
|
||||
return {
|
||||
"default": _cross_grid(layout.get("default") or CROSS_GRID_DEFAULT),
|
||||
"sheets": {str(int(key)): _cross_grid(grid) for key, grid in sheets.items()},
|
||||
}
|
||||
|
||||
|
||||
def write_cross_layout(
|
||||
project_root: Path, longitudinal_path: Path, layout: dict[str, Any]
|
||||
) -> list[int | float]:
|
||||
"""배치를 쓰고 담긴 측점이 바뀐 확정 장을 푼다 — 풀린 장의 옛 측점들(설계 상태를 되돌릴 몫)."""
|
||||
checked = checked_cross_layout(layout)
|
||||
before = _cross_sheet_plan(project_root, longitudinal_path)
|
||||
atomic_write_json(_design_root(project_root) / CROSS_LAYOUT_FILE, checked)
|
||||
after = {
|
||||
sheet["id"]: sheet["chainages"]
|
||||
for sheet in _cross_sheet_plan(project_root, longitudinal_path)
|
||||
}
|
||||
manifest = _read_manifest(project_root)
|
||||
stale: list[int | float] = []
|
||||
for sheet in before:
|
||||
entry = manifest["drawings"].get(sheet["id"])
|
||||
if entry and entry.get("confirmed") and after.get(sheet["id"]) != sheet["chainages"]:
|
||||
entry["confirmed"] = False
|
||||
stale.extend(sheet["chainages"])
|
||||
if stale:
|
||||
_write_manifest(project_root, manifest)
|
||||
return stale
|
||||
|
||||
|
||||
def _drawing_list(project_root: Path, longitudinal_path: Path) -> list[DesignDrawingItem]:
|
||||
longitudinal = _read_json(longitudinal_path)
|
||||
station_by_chainage = _station_map(longitudinal)
|
||||
manifest_drawings = _read_manifest(project_root)["drawings"]
|
||||
@@ -123,7 +174,7 @@ def _drawing_list(
|
||||
# 횡단 장 계획이 실패해도 나머지 도면은 목록에 남긴다 — 예전에는 `cross_sections/`
|
||||
# 하나가 없으면 표지·종단면도·토적도·유역도까지 함께 사라졌다(2026-09-01 지적).
|
||||
try:
|
||||
sheets = _cross_sheet_plan(project_root, longitudinal_path, designs, overlays)
|
||||
sheets = _cross_sheet_plan(project_root, longitudinal_path)
|
||||
except (FileNotFoundError, ValueError, OSError) as exc:
|
||||
logger.warning("B07 횡단 장 계획 실패 — 나머지 도면만 싣는다: %s", exc)
|
||||
sheets = []
|
||||
@@ -190,32 +241,21 @@ def _store_drawing_numbers(project_root: Path, drawings: list[DesignDrawingItem]
|
||||
_write_manifest(project_root, manifest)
|
||||
|
||||
|
||||
def _cross_sheet_plan(
|
||||
project_root: Path,
|
||||
longitudinal_path: Path,
|
||||
designs: dict[int | float, dict[str, Any]] | None = None,
|
||||
overlays: dict[int | float, tuple[float, float, float, float]] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
def _cross_sheet_plan(project_root: Path, longitudinal_path: Path) -> list[dict[str, Any]]:
|
||||
"""측점 도면을 A1 장으로 나눈 계획 — 목록과 도면 생성이 같은 결과를 쓴다.
|
||||
|
||||
한 장에 몇 개가 들어가는지는 단면 블록 크기가 정한다(작성 척도 1/100 +
|
||||
설계선이 원지반과 갈라지는 구간). 설계 지정이 없으면 원지반 기준 폭이 된다.
|
||||
장마다 열 × 행 칸 수만큼 담는다(140-3 · 단면 크기와 무관 — 설계 · 덧그림에 장 id 가 안 흔들림).
|
||||
"""
|
||||
longitudinal = _read_json(longitudinal_path)
|
||||
blocks: list[tuple[int, float, float]] = []
|
||||
chainages: list[int | float] = []
|
||||
for path in sorted(_cross_files(longitudinal_path, longitudinal)):
|
||||
match = _CROSS_ID.fullmatch(path.stem)
|
||||
if not match:
|
||||
continue
|
||||
source = _read_json(path)
|
||||
# 측점은 파일 속 값으로 — 옛 프로젝트는 소수 측점 파일이 정수 m 이름이라 이름으로 못 앎
|
||||
chainage = cross_station(source.get("chainage_m") or parse_cross_key(match.group(1)))
|
||||
design = (designs or {}).get(chainage)
|
||||
design_line = _cross_design_line(longitudinal_path, source, design)
|
||||
overlay = (overlays or {}).get(chainage) # 화면 덧그림 범위(68-3)
|
||||
width, height = section_block_size(source, design_line, design=design, overlay=overlay)
|
||||
blocks.append((chainage, width, height))
|
||||
return plan_cross_sheets(blocks)
|
||||
chainages.append(cross_station(source.get("chainage_m") or parse_cross_key(match.group(1))))
|
||||
return plan_cross_sheets(chainages, read_cross_layout(project_root))
|
||||
|
||||
|
||||
def _quantity_table(
|
||||
@@ -353,7 +393,7 @@ def _read_cross_sheet(
|
||||
"""횡단 장 하나를 만든다. stored_designs는 {측점: 설계 지정}, overlays는 {측점: 덧그림 범위}."""
|
||||
designs = stored_designs if isinstance(stored_designs, dict) else {}
|
||||
overlays = overlays or {}
|
||||
plan = _cross_sheet_plan(project_root, longitudinal_path, designs, overlays)
|
||||
plan = _cross_sheet_plan(project_root, longitudinal_path)
|
||||
sheet = next((item for item in plan if item["id"] == drawing_id), None)
|
||||
if sheet is None:
|
||||
raise FileNotFoundError("요청한 횡단 장을 찾을 수 없습니다.")
|
||||
|
||||
Reference in New Issue
Block a user