fix(B07): 횡단면도 박스가 화면 덧그림(B06 설계선·구조물)까지 1 mm 로 감쌈

- 덧그림 범위를 서버가 화면과 같은 TS 로 셈 — Node 진입점 B07_DesignDetail_Cross_Extent_Node(최소 DOM 위에서 오프스크린 SVG 그리기 그대로) · 번들 build:b07-extent
- 화면 그리기·수확 부분을 B07_DesignDetail_Cross_Overlay 로 떼어 화면과 Node 가 한 벌로 씀
- 장 배치·블록 테두리가 측점별 덧그림 범위를 덮음(목록·장 열기·확정·수정 네 길 모두 같은 계획)
- 글자는 CAD 그리기대로 세로 가운데 · 폭은 글꼴 어림(한글 1.0 · 나머지 0.6 배)
- 번들 낡음 판정에 B07 맨 위 TS 포함
- 검증_sub4: 66개 박스 ↔ 그림(덧그림·글자 실폭 포함) 모두 1.0 mm · 잘림 0 · 겹침 0 · 20장 → 21장

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013rJwJMVL4EE31y77TB11AP
This commit is contained in:
2026-10-06 23:33:53 +09:00
co-authored by Claude Opus 5.5
parent 86e61bd69d
commit e4fa14564f
13 changed files with 986 additions and 464 deletions
@@ -111,6 +111,7 @@ 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]:
longitudinal = _read_json(longitudinal_path)
station_by_chainage = _station_map(longitudinal)
@@ -128,7 +129,7 @@ def _drawing_list(
# 횡단 장 계획이 실패해도 나머지 도면은 목록에 남긴다 — 예전에는 `cross_sections/`
# 하나가 없으면 표지·종단면도·토적도·유역도까지 함께 사라졌다(2026-09-01 지적).
try:
sheets = _cross_sheet_plan(project_root, longitudinal_path, designs)
sheets = _cross_sheet_plan(project_root, longitudinal_path, designs, overlays)
except (FileNotFoundError, ValueError, OSError) as exc:
logger.warning("B07 횡단 장 계획 실패 — 나머지 도면만 싣는다: %s", exc)
sheets = []
@@ -214,6 +215,7 @@ 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]]:
"""측점 도면을 A1 장으로 나눈 계획 — 목록과 도면 생성이 같은 결과를 쓴다.
@@ -231,7 +233,8 @@ def _cross_sheet_plan(
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)
width, height = section_block_size(source, design_line, design=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)
@@ -342,6 +345,7 @@ def _cross_section_input(
longitudinal: dict[str, Any],
chainage: int | float,
design: dict[str, Any] | None,
overlay: tuple[float, float, float, float] | None = None,
) -> dict[str, Any] | None:
"""한 측점의 장 배치 입력(원본·계획선·수량표·제목)을 만든다."""
path = find_cross_file(longitudinal_path.parent.parent / "cross_sections", chainage)
@@ -356,6 +360,7 @@ def _cross_section_input(
"design_line": _cross_design_line(longitudinal_path, source, design),
"quantity_table": _quantity_table(source, design),
"title": station_no_label(float(source.get("chainage_m", chainage)), interval),
"overlay": overlay,
}
@@ -364,10 +369,12 @@ def _read_cross_sheet(
longitudinal_path: Path,
drawing_id: str,
stored_designs: dict[str, Any] | None,
overlays: dict[int | float, tuple[float, float, float, float]] | None = None,
) -> tuple[str, str, dict[str, Any], bool, dict[str, float | None] | None]:
"""횡단 장 하나를 만든다. stored_designs는 {측점: 설계 지정} 묶음이다."""
"""횡단 장 하나를 만든다. stored_designs는 {측점: 설계 지정}, overlays는 {측점: 덧그림 범위}."""
designs = stored_designs if isinstance(stored_designs, dict) else {}
plan = _cross_sheet_plan(project_root, longitudinal_path, designs)
overlays = overlays or {}
plan = _cross_sheet_plan(project_root, longitudinal_path, designs, overlays)
sheet = next((item for item in plan if item["id"] == drawing_id), None)
if sheet is None:
raise FileNotFoundError("요청한 횡단 장을 찾을 수 없습니다.")
@@ -375,7 +382,13 @@ def _read_cross_sheet(
sections = [
section
for section in (
_cross_section_input(longitudinal_path, longitudinal, chainage, designs.get(chainage))
_cross_section_input(
longitudinal_path,
longitudinal,
chainage,
designs.get(chainage),
overlays.get(chainage),
)
for chainage in sheet["chainages"]
)
if section is not None
@@ -389,6 +402,7 @@ def _read_drawing(
longitudinal_path: Path,
drawing_id: str,
stored_design: dict[str, Any] | None = None,
overlays: dict[int | float, tuple[float, float, float, float]] | None = None,
) -> tuple[str, str, dict[str, Any], bool, dict[str, float | None] | None]:
"""(kind, label, drawing, confirmed, quantity_table)를 반환한다.
@@ -522,7 +536,9 @@ def _read_drawing(
)
if CROSS_SHEET_ID.fullmatch(drawing_id):
return _read_cross_sheet(project_root, longitudinal_path, drawing_id, stored_design)
return _read_cross_sheet(
project_root, longitudinal_path, drawing_id, stored_design, overlays
)
cross_match = _CROSS_ID.fullmatch(drawing_id)
if not cross_match: