- `pipe.revet_foundation`(기슭막이 기초) 신설 — **유입·유출 한 칸**. 한 공사에서 한쪽만 기초유로 하는 일이 드물어 폼을 둘로 안 늘렸다. 실무가 갈라 쓰면 그때 나눌 것. - 독립 기슭막이·돌쌓기 갈래도 이음: 구조물 정본 `foundation` → `RevetmentSpec` → `RevetmentLayout.foundation` → 터파기 그림. ⚠ 이름을 저장 칸과 **같은 글자**로 뒀다. - ⑨ `stone_coeff_basis`(품셈/실무 관행) · ⑩ `fill_concrete_mpa`(180/210) 칸 신설. ⚠ **기본값 없음** — 「비어 있음 = 아직 안 정함」이고 기본값(⑨ 품셈 · ⑩ 210)은 계산 쪽이 갖는다. - 저장 왕복 확인: PUT 200 → 되읽기에서 "실무 관행"·"180"·"기초유" 그대로. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
119 lines
5.8 KiB
Python
119 lines
5.8 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"),
|
|
}
|
|
)
|
|
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
|