Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
122 lines
6.1 KiB
Python
122 lines
6.1 KiB
Python
"""구조물 정본(C군 사면안정 벽)을 횡단 측점에 얹는다.
|
|
|
|
왜 필요한가(2026-09-06 사용자 확정) — 좌측 「구조물 배치」로 넣은 옹벽·돌쌓기 같은 벽이
|
|
횡단도에도 서고 **절·성토 면적에도 반영**돼야 한다. 지금까지 이 목록은 측점만 심고
|
|
(`B05_Profile_Engine_Sections.resolve_extra_stations`) 기하가 없어 면적이 그대로였다.
|
|
|
|
방법은 **이미 도는 길을 그대로 태우는 것**이다. 독립 기슭막이가 쓰는 `section.revetment`
|
|
제원과 같은 꼴로 얹으면 횡단 기하(`B06_Section_UI_Cross_Revetment`)·설계선 트림·폐회로
|
|
면적(`B06_Section_Structure_Layouts`)·3D 가 손대지 않고 따라온다. 옛 D군 기슭막이가
|
|
관 정본으로 이관되며 사라졌던 `attach_revetments`(2026-08-28)를 C군 벽으로 되살린 것이다.
|
|
|
|
치수는 여기서 정하지 않는다 — 높이·형태만 넘기고 도형은 화면·3D 가 같은 산식으로 그린다.
|
|
|
|
**짝**: `common_util/common_util_structure_walls.ts` — 브라우저는 아직 저장하지 않은
|
|
목록으로 같은 제원을 만들어야 해서 한 벌을 더 둔다. 거울 테스트
|
|
`tmp/tests/test_b06_structure_walls_mirror.py` 가 같은 값이 나오는지 대조한다.
|
|
"""
|
|
|
|
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
|
|
|
|
# 구조물 종류 → 횡단 기하가 아는 **형태** 이름. 형태가 벽 높이 한계·두께를 정하므로
|
|
# (`B06_Section_UI_Cross_Revetment.revetHeightLimit`) 가장 가까운 것으로 잇는다.
|
|
_FORM_BY_TYPE = {
|
|
"masonry_wet": "돌쌓기(찰)",
|
|
"masonry_dry": "돌쌓기(메)",
|
|
"boulder_masonry": "돌쌓기(메)",
|
|
"retaining_wall": "콘크리트",
|
|
"soil_guard": "통나무·목재틀",
|
|
}
|
|
|
|
|
|
# 소단 타입 id — 레지스트리와 한 벌이다(C군이지만 벽이 아니다).
|
|
BERM_TYPE_ID = "berm"
|
|
|
|
|
|
def load_wall_structures(project_root: Path) -> list[dict[str, Any]]:
|
|
"""구조물 정본에서 C군 벽(구간형) 목록을 읽는다. 실패하면 빈 목록."""
|
|
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 != "C" or definition.placement != "interval":
|
|
continue
|
|
# 소단은 C군 구간형이지만 **벽이 아니다** — 사면을 계단으로 끊는 시설이라
|
|
# 기슭막이 제원 자리에 얹으면 안 된다(계획서 3-9).
|
|
if structure.type_id == BERM_TYPE_ID:
|
|
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()),
|
|
"form": options.get("form") or _FORM_BY_TYPE.get(structure.type_id),
|
|
"height_m": options.get("height_m"),
|
|
# C군 폼에는 설치 측 칸이 없다 — 비워 두면 화면이 **성토가 나는 쪽**으로
|
|
# 세운다(`computeRevetmentLayout`). 사용자가 정하고 싶어지면 그때 칸을 낸다.
|
|
"side": options.get("side"),
|
|
# 기초 축(기초유/기초버림) — 터파기 그림·물량이 같이 쓴다.
|
|
# 비어 있으면 화면이 터파기를 안 그린다(근거 없음).
|
|
"foundation": options.get("foundation"),
|
|
"tiers": options.get("tiers"),
|
|
"lift_m": options.get("lift_m"),
|
|
"shift_m": options.get("shift_m"),
|
|
# 전면 기울기를 B08 과 같은 칸으로 가르는 값 — 저장 원본(2026-09-14 B5 · TS 짝 같은 키).
|
|
"face_role": options.get("face_role"),
|
|
"face_slope_ratio": options.get("face_slope_ratio"),
|
|
}
|
|
)
|
|
return found
|
|
except Exception: # noqa: BLE001 — 정본을 못 읽어도 횡단 조회는 이어 간다
|
|
logger.exception("B06 구조물 벽 정본을 읽지 못했습니다 (없는 것으로 본다)")
|
|
return []
|
|
|
|
|
|
def attach_wall_structures(project_root: Path, cross_sections: list[dict[str, Any]]) -> int:
|
|
"""구간 안 측점의 횡단 dict에 `revetment` 키를 얹는다. 얹은 개수를 돌려준다.
|
|
|
|
이미 관 정본이 얹은 세트(`culvert`·`revetment`)가 있는 측점은 **건드리지 않는다** —
|
|
관 유입·유출 벽과 구조물 벽이 한 자리에 겹치면 어느 쪽 그림인지 읽히지 않는다.
|
|
한 측점에 여러 개가 겹치면 먼저 시작한 것을 쓴다(겹침 정리는 사용자 몫).
|
|
"""
|
|
walls = load_wall_structures(project_root)
|
|
if not walls:
|
|
return 0
|
|
attached = 0
|
|
for section in cross_sections:
|
|
chainage = section.get("chainage_m")
|
|
if not isinstance(chainage, (int, float)):
|
|
continue
|
|
if section.get("culvert") or section.get("revetment"):
|
|
continue
|
|
for spec in walls:
|
|
if (
|
|
spec["start_m"] - _EDGE_TOLERANCE_M
|
|
<= float(chainage)
|
|
<= spec["end_m"] + _EDGE_TOLERANCE_M
|
|
):
|
|
section["revetment"] = spec
|
|
attached += 1
|
|
break
|
|
if attached:
|
|
logger.info("B06 구조물 벽 %d개 측점에 얹음 (구조물 %d건)", attached, len(walls))
|
|
return attached
|