- 옛 노선 지문 관 파일(옛 누가거리 394 · 좌표 351) — 측점 계획 · 관 지점 GET · 화면 · 상세 얹기는 351 인데 세월교 노면 하강(ford_surface_drops) · 물넘이포장 구간(ford_pavement_ranges · tilt_ranges) · 카드 이름 (relabel_pipe_stations) · B07 관 글 재료(load_note_sources) · 옛 비정규 측점 이관 심는 자리만 394 로 읽음 → [저장] 재계산 · [확정] · 상세 값이 화면과 갈림 - B05_Profile_Engine_Sections.route_pipe_points — 관 지점 정본을 지금 노선에 얹어 읽음(_load_pipe_points 이월) · 노선 경로 안 줌 = 노선 파일 하나(ROUTE_DATA_FILE · 노선 풀기도 같은 이름을 씀) · 노선 못 읽으면 저장분 그대로 - load_culvert_sets · attach_saved_shapes(saved_pipes 걷음) · relabel_pipe_stations(상세 GET 은 노선 경로 넘김) · B07 load_note_sources · Structures_Router 이관 심는 자리 = 이 한 벌 - 시험 test_472_d_route_pipe_readers(옛 코드 빨강 2) · test_472_d_pipe_route_attach 「노선 모름 = 파일 누가거리」 단언은 옛 동작이라 「노선 파일 하나 = 351」로 고침 · 위키 rules · calculations 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
147 lines
7.7 KiB
Python
147 lines
7.7 KiB
Python
"""B06 관 세트 읽기 · 측점에 얹기 — `B06_Section_Engine_Culvert.py` 에서 나눔(472 덧 · 동작 같음).
|
|
|
|
관 지점 정본(`pipe_points.json`) → 누가거리별 세트 제원(`culvert_sets_from`) → 횡단 dict 에
|
|
`culvert` · `ford` · `box` · `ford_pavement` 키로 얹는다(`attach_culvert_specs`). 세트 제원 만들기
|
|
(`_culvert_set` 등)는 `B06_Section_Engine_Culvert.py`.
|
|
|
|
⚠ TS 짝: `common_util/common_util_culvert_sets.ts` `buildCulvertSets` · `attachCulvertSets` —
|
|
거울 시험 `resources/tester/test_b06_culvert_sets_mirror.py`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from B06_Section.B06_Section_Engine_Culvert import (
|
|
_box_set,
|
|
_culvert_set,
|
|
_ford_pavement_set,
|
|
_ford_set,
|
|
_number,
|
|
_revet_set,
|
|
)
|
|
from common_util.common_util_structure_catalog import catalog_row
|
|
from common_util.common_util_structure_station import EDGE_TOLERANCE_M, PIPE_MATCH_M
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# 관 지점과 횡단 측점을 같은 자리로 볼 허용 오차(m) — 같은 자리 거리 한 곳(323-7)
|
|
_CHAINAGE_TOLERANCE_M = EDGE_TOLERANCE_M
|
|
|
|
# 세트 종류 -> 횡단 dict 키. 그림이 다른 시설끼리 소비처가 섞이지 않게 나눠 둔다.
|
|
_SECTION_KEYS = {"ford": "ford", "box": "box", "ford_pavement": "ford_pavement"}
|
|
|
|
# 구조물 종류별 **인접 측점 연동**(2026-08-25 사용자 확정). 이름을 넣으면 도로 방향
|
|
# 폭(`span_m`)의 절반만큼 앞뒤 측점에도 같은 스펙이 붙어 횡단도가 한 벌 더 그려진다.
|
|
# 세월교·BOX암거는 끔 — 소유 측점 한 곳에만 선다. 3D는 그 측점에서 `span_m` 전체를
|
|
# 스윕하므로 길이가 줄지 않는다(인접 측점 기준으로 자르지 않는다).
|
|
# 물넘이포장만 켠다 — 노면을 월류 폭만큼 파는 시설이라 **범위 안 모든 횡단도**에
|
|
# 파임이 그려져야 한다(2026-08-28 사용자 확정).
|
|
# ※ 기슭막이 연동은 이 경로가 아니다 — 프론트 `culvertOwnerFor`가 따로 판정한다.
|
|
_SPAN_LINKED_TYPES: frozenset[str] = frozenset({"ford_pavement"})
|
|
|
|
|
|
def load_culvert_sets(project_root: Path) -> dict[float, dict[str, Any]]:
|
|
"""관 지점 정본 → 누가거리별 세트 제원 — 관 = 지금 노선에 얹은 자리(472 덧 · 측점 계획과 같음).
|
|
|
|
파일 누가거리를 그대로 읽으면 지문이 어긋난 옛 저장분에서 세월교 노면 하강 · 물넘이포장 구간이
|
|
측점 계획 · 화면과 다른 자리에 걸렸다. 파일이 없거나 깨졌으면 빈 값.
|
|
"""
|
|
from B05_Profile.B05_Profile_Engine_Sections import route_pipe_points
|
|
|
|
return culvert_sets_from(route_pipe_points(project_root))
|
|
|
|
|
|
def culvert_sets_from(points: list[Any]) -> dict[float, dict[str, Any]]:
|
|
"""관 지점 목록 → 누가거리(소수 2자리)별 세트 제원(TS `buildCulvertSets` 짝 · 형상 얹기 한 벌 323-4)."""
|
|
sets: dict[float, dict[str, Any]] = {}
|
|
for point in points:
|
|
# 그릴 세트 = 구조물 표 형상(322-1 · TS `buildCulvertSets` 짝)
|
|
shape = (catalog_row(point.facility) or {}).get("shape")
|
|
if shape == "pipe_set":
|
|
if (point.options or {}).get("pipe_diameter_mm") in (None, ""):
|
|
continue # 관경 안 고름 = 관 없음(54-39)
|
|
spec = _culvert_set(point.options)
|
|
elif shape == "ford":
|
|
spec = _ford_set(point.options)
|
|
elif shape == "box":
|
|
spec = _box_set(point.options)
|
|
elif shape == "ford_pavement":
|
|
# 구조물이 아니라 파인 노면이다 — 스펙 모양도 소비처도 다르다(2026-08-28).
|
|
spec = _ford_pavement_set(point.options)
|
|
elif shape == "hidden_revet":
|
|
# 독립 기슭막이 — 배관 세트 모양이되 관을 숨긴다. `culvert` 키로 얹혀 배관
|
|
# 경로(컴퓨트·렌더·패널·연동·경사·3D·수량)를 그대로 탄다.
|
|
spec = _revet_set(point.options)
|
|
else:
|
|
continue
|
|
sets[round(float(point.chainage_m), 2)] = spec
|
|
return sets
|
|
|
|
|
|
def attach_culvert_sets(project_root: Path, cross_sections: list[dict[str, Any]]) -> int:
|
|
"""배관이 놓인 측점의 횡단 dict에 `culvert` 키를 얹는다. 얹은 개수를 돌려준다."""
|
|
return attach_culvert_specs(cross_sections, load_culvert_sets(project_root))
|
|
|
|
|
|
def attach_culvert_specs(
|
|
cross_sections: list[dict[str, Any]],
|
|
sets: dict[float, dict[str, Any]],
|
|
missed: list[float] | None = None,
|
|
) -> int:
|
|
"""세트를 측점에 얹음(TS `attachCulvertSets` 짝) · 얹은 수 · 주인 먼 세트 = `missed`."""
|
|
if not sets:
|
|
return 0
|
|
attached = 0
|
|
# 주인 측점 = 가장 가까운 측점 하나(`PIPE_MATCH_M` 안 · 정수 m 측점 위 소수 관도) ·
|
|
# 더 멀면 그 시설 측점이 없음(노선 바뀜) — 연동 이웃에도 안 얹음.
|
|
owner: dict[float, float] = {}
|
|
for pipe_chainage in sets:
|
|
nearest = None
|
|
for section in cross_sections:
|
|
value = _number(section.get("chainage_m"), None)
|
|
if value is None:
|
|
continue
|
|
if nearest is None or abs(value - pipe_chainage) < abs(nearest - pipe_chainage):
|
|
nearest = value
|
|
if nearest is not None and abs(nearest - pipe_chainage) < PIPE_MATCH_M:
|
|
owner[pipe_chainage] = nearest
|
|
elif missed is not None:
|
|
missed.append(pipe_chainage)
|
|
for section in cross_sections:
|
|
chainage = _number(section.get("chainage_m"), None)
|
|
if chainage is None:
|
|
continue
|
|
# ⚠ 키가 다른 시설은 같은 측점에 함께 얹는다(299-1) — 첫 시설 하나에서 멈추면 물넘이포장
|
|
# 연동 범위(폭 절반) 안 측점의 **자기 관**이 빠졌다(95 m 배관이 90 m 물넘이에 가려짐).
|
|
# 같은 키끼리는 소유 측점 것 → 흙막이 아닌 자기 시설 → 가까운 것 → 관 지점 차례.
|
|
best: dict[str, tuple[tuple[Any, ...], dict[str, Any]]] = {}
|
|
for order, (pipe_chainage, spec) in enumerate(sets.items()):
|
|
if pipe_chainage not in owner:
|
|
continue
|
|
# 연동 대상 종류만 폭의 절반까지 옆 측점에 걸친다(`_SPAN_LINKED_TYPES`).
|
|
reach = _CHAINAGE_TOLERANCE_M
|
|
if spec.get("type") in _SPAN_LINKED_TYPES:
|
|
reach += (_number(spec.get("span_m"), 0.0) or 0.0) / 2
|
|
owned = owner.get(pipe_chainage) == chainage
|
|
if not (abs(chainage - pipe_chainage) <= reach or owned):
|
|
continue
|
|
# 세월교·BOX암거·물넘이는 배수관과 그림이 달라 키를 나눈다 — 소비처가 섞이지 않는다.
|
|
entries = [(_SECTION_KEYS.get(str(spec.get("type")), "culvert"), spec, False)]
|
|
# 세월교 · 박스 흙막이(73-4) — 배관 흙막이와 같은 자리(`culvert`)에 함께 얹음 ·
|
|
# 자기 관과 부딪히면 관 먼저(흙막이가 덮으면 관 그림 · 연장이 빠짐).
|
|
if spec.get("guard"):
|
|
entries.append(("culvert", spec["guard"], True))
|
|
for key, value, is_guard in entries:
|
|
rank = (not owned, is_guard, abs(chainage - pipe_chainage), order)
|
|
if key not in best or rank < best[key][0]:
|
|
# ⚠ 스펙에 **그 시설이 놓인 누가거리**를 함께 얹음 — 없으면 소비처가 소유 측점을
|
|
# 못 가려 **같은 시설을 여러 측점에서 셈**(폭 절반 연동 · 관 9개에 길이 10곳).
|
|
best[key] = (rank, {**value, "chainage_m": pipe_chainage})
|
|
for key, (_, value) in best.items():
|
|
section[key] = value
|
|
attached += bool(best)
|
|
return attached
|