fix(458 B06 노선 바뀜): 주인 측점 거리 상한 0.51 m · 시종점 밖 시설 안 얹음 · 갈아끼움 grid 지움 · 늘 다시 얹기 · 이름 = 계획 제목

- 관 세트 주인 측점 = 0.51 m 안 가장 가까운 측점(TS attachCulvertSets ↔ 파이썬 attach_culvert_specs) · 넘으면 연동 이웃에도 안 얹고 missed
- 형상 얹기 한 벌(attachShapes ↔ attach_shapes) — 시종점 측점 자리인데 좌표가 노선 진행 반대쪽 0.51 m 넘게 바깥 = 「노선 밖」 · 안 얹음 · 안 얹은 것 돌려줌(서버 = 로그 경고)
- 측점 갈아끼움 — 격자 아닌 자리면 앞 횡단 grid 표시 지움 · 갈아끼움 뒤 가게 초안 없어도 늘 다시 얹음 · 카드 · 종단 측점 이름 = 계획 제목 · 안 얹은 시설 알림(바뀐 때 한 번)
- 시험: test_458_swap_attach(재현 A · D) · test_323_4 거울 moved 경우(상한 · 시종점 밖 · 연동 폭) · test_299 같은 키 견본 = 주인 0.51 m 안으로 · 273_2 흉내 돌려줌

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 22:44:41 +09:00
co-authored by Claude Opus 5.5
parent 0c70775712
commit 37a738aff4
13 changed files with 475 additions and 47 deletions
+50 -3
View File
@@ -9,6 +9,7 @@ TS 짝 = `common_util/common_util_structure_attach.ts` `attachShapes`(화면 =
from __future__ import annotations
import logging
import math
from pathlib import Path
from typing import Any
@@ -28,6 +29,7 @@ from common_util.common_util_structure_pavement import (
pavement_specs_from,
)
from common_util.common_util_structure_station import (
PIPE_MATCH_M,
SAME_STATION_M,
blank_keys,
plan_stations,
@@ -78,12 +80,54 @@ def _attach_blanks(sections: list[dict[str, Any]], pipes: list[Any], structures:
section["blank"] = labels
def attach_shapes(sections: list[dict[str, Any]], pipes: list[Any], structures: list[Any]) -> None:
"""측점마다 그림 자료를 걷고 현황으로 다시 얹음(TS `attachShapes` 짝 · 제자리 고침)."""
def _finite(value: Any) -> bool:
return isinstance(value, (int, float)) and math.isfinite(value)
def _outside_route(pipe: Any, sections: list[dict[str, Any]]) -> bool:
"""시종점 밖으로 밀린 관인가(458 · TS `outsideRoute` 짝) — 시종점 측점 자리인데 좌표가 노선 진행
반대쪽으로 `PIPE_MATCH_M` 넘게 바깥(옆 거리는 안 봄 · 좌표 없으면 아님)."""
placed = sorted(
(
s
for s in sections
if all(_finite(s.get(k)) for k in ("chainage_m", "center_x", "center_y"))
),
key=lambda s: s["chainage_m"],
)
x, y = getattr(pipe, "x", None), getattr(pipe, "y", None)
if len(placed) < 2 or not (_finite(x) and _finite(y)):
return False
for end, nxt in ((placed[0], placed[1]), (placed[-1], placed[-2])):
if abs(end["chainage_m"] - float(pipe.chainage_m)) >= PIPE_MATCH_M:
continue
dx, dy = nxt["center_x"] - end["center_x"], nxt["center_y"] - end["center_y"]
span = math.hypot(dx, dy)
if (
span > 0
and ((x - end["center_x"]) * dx + (y - end["center_y"]) * dy) / span <= -PIPE_MATCH_M
):
return True
return False
def attach_shapes(
sections: list[dict[str, Any]], pipes: list[Any], structures: list[Any]
) -> dict[str, list[float]]:
"""측점마다 그림 자료를 걷고 현황으로 다시 얹음(TS `attachShapes` 짝 · 제자리 고침) ·
시종점 밖 관 · 주인 측점 먼 관은 안 얹고 돌려줌(458 · `{"outside", "far"}`)."""
for section in sections:
for key in (*SHAPE_KEYS, "blank"):
section.pop(key, None)
attach_culvert_specs(sections, culvert_sets_from(pipes))
misses: dict[str, list[float]] = {"outside": [], "far": []}
kept = []
for pipe in pipes:
if _outside_route(pipe, sections):
misses["outside"].append(float(pipe.chainage_m))
else:
kept.append(pipe)
pipes = kept
attach_culvert_specs(sections, culvert_sets_from(pipes), misses["far"])
# 관 세트가 선 측점은 벽을 안 얹음(`attach_wall_specs`) — 차례 = 화면과 같음
attach_wall_specs(sections, wall_specs_from(structures))
attach_pavement_specs(
@@ -92,6 +136,9 @@ def attach_shapes(sections: list[dict[str, Any]], pipes: list[Any], structures:
)
attach_underdrain_specs(sections, underdrains_from(structures))
_attach_blanks(sections, pipes, structures)
if misses["outside"] or misses["far"]:
logger.warning("B06 시설 그림 안 얹음 — 노선 밖 %s · 측점 멂 %s", *misses.values())
return misses
def attach_saved_shapes(project_root: Path, sections: list[dict[str, Any]]) -> None: