"""독립 기슭막이(구조물 정본 D군) 제원을 횡단 측점에 얹는다. 배관 유입·유출에 딸린 기슭막이는 관 정본이 관리하지만(`B06_Section_Engine_Culvert`), 배관과 무관한 **독립 기슭막이**는 구조물 정본(`structures.json`)이 정본이다. 여기서는 구간(시작~종료) 안의 측점에 형태·높이·설치 측을 붙이기만 한다 — 치수 결정·도형은 화면(`B06_Section_UI_Cross_Revetment`)과 3D가 같은 산식으로 그린다(2026-08-28 사용자 확정). 측점 자체는 `B05_Profile_Engine_Sections.resolve_extra_stations`가 시작·기준·종료에 심는다. """ import logging from pathlib import Path from typing import Any from B05_Profile.B05_Profile_Structures_Repository import load_structures from B05_Profile.B05_Profile_Structures_Schema import structure_type_map logger = logging.getLogger(__name__) # 구간 경계 측점을 구간 안으로 볼 허용 오차(m) — 정본이 누가거리를 0.01m로 끊어 쓴다. _EDGE_TOLERANCE_M = 0.02 def load_revetments(project_root: Path) -> list[dict[str, Any]]: """구조물 정본에서 독립 기슭막이(D군 구간형) 목록을 읽는다. 실패하면 빈 목록.""" try: types = structure_type_map() found: list[dict[str, Any]] = [] for structure in load_structures(str(project_root))[1]: definition = types.get(structure.type_id) if definition is None or definition.group != "D" or definition.placement != "interval": continue start, end = structure.start_m, structure.end_m if start is None or end is None: continue options = structure.options or {} found.append( { "structure_id": structure.structure_id, "type_id": structure.type_id, "name": definition.name, "start_m": float(min(start, end)), "end_m": float(max(start, end)), "anchor_m": float(structure.anchor_m()), # 형태·높이는 **배관 유출 키 한 벌**이 정본이다(B05 폼 2026-08-28 # 이관). 옛 전용 키(form·height_m)는 읽을 때만 폴백으로 본다 — # 이걸 안 보면 형태가 비어 도형이 늘 메쌓기로 그려졌다(2026-08-30). "form": options.get("outlet_revet_form") or options.get("form"), "height_m": ( options.get("outlet_revet_height_m") if options.get("outlet_revet_height_m") is not None else options.get("height_m") ), "side": options.get("side"), # 다단 — 1이면 단일 벽. 전개 규칙은 화면·3D가 같은 산식으로 푼다. "tiers": options.get("tiers"), # 자리 이동(사용자 조작) — 사면 위로 올림 / 좌우 이동. "lift_m": options.get("lift_m"), "shift_m": options.get("shift_m"), } ) return found except Exception: # noqa: BLE001 — 정본을 못 읽어도 횡단 조회는 이어 간다 logger.exception("B06 독립 기슭막이 정본을 읽지 못했습니다 (없는 것으로 본다)") return [] def attach_revetments(project_root: Path, cross_sections: list[dict[str, Any]]) -> int: """구간 안 측점의 횡단 dict에 `revetment` 키를 얹는다. 얹은 개수를 돌려준다. 한 측점에 여러 개가 겹치면 **먼저 시작한 것**을 쓴다 — 겹침 정리는 사용자 몫이다. """ revetments = load_revetments(project_root) if not revetments: return 0 attached = 0 for section in cross_sections: chainage = section.get("chainage_m") if not isinstance(chainage, (int, float)): continue for spec in revetments: if ( spec["start_m"] - _EDGE_TOLERANCE_M <= float(chainage) <= spec["end_m"] + _EDGE_TOLERANCE_M ): section["revetment"] = spec attached += 1 break return attached