Files
Aislo/B06_Section/B06_Section_Engine_Attach.py
T
eomsangdonandClaude Opus 5.5 c45814f910 feat(구조물표): 323-4 형상 얹기 한 벌(TS · 파이썬 짝) — 저장분 · 초안 · 미리 뜬 횡단 같은 길(저장 전 그림 = 저장 뒤 그림) · 빈칸 경고 한 꼴(카드 칩 「종류 칸 빈칸」) · 3D 지문 = 그림 자료 통째 · C군 벽 3D 한 번 · 소단 원천 = 표 형상 berm
- 짝 `common_util_structure_attach.ts` attachShapes ↔ `B06_Section_Engine_Attach.py` attach_shapes — 열쇠 걷고 관 세트 → 벽 → 포장 → 맹암거 → 빈칸 경고(측점 계획 자리 · 「옹벽 높이」 · 「배관 관경」 · 「맹암거 암거 높이」 · 「물넘이포장 월류 높이」)
- 서버 `_read_section_detail` · 재생성 응답 = attach_saved_shapes 하나 · 화면 `withDraftShapes`(현황 = 초안 ?? 저장분 · 초안 없어지면 저장분으로 한 번) — decorate 관 세트 · withDraftPipeKinds · 맹암거 · 포장 · 벽 블록 걷음 · 구조물 초안 없을 때 미리 뜬 횡단에 포장 · 벽 · 맹암거 빠지던 것 · 반폭 넓힘 뒤 날 상세(초안 빠짐) 고침
- 파이썬 얹기 = 목록 함수(culvert_sets_from · attach_culvert_specs · wall_specs_from · attach_wall_specs · underdrains_from · attach_underdrain_specs) + 옛 파일 함수는 감쌈
- 3D 지문 = SHAPE_KEYS(`common_util_structure_shape_keys.ts`) + 조작값 통째(손 해시 걷음 · 벽 기초 · 단 · 포장 · 맹암거 빠짐) · C군 벽 = 기준 측점과 가장 가까운 측점에서만 시작 ~ 끝 스윕(`wallSweepAt`)
- 소단 = 표 형상 berm(BERM_TYPE_ID 걷음 · 화면 isBermType · 서버 catalog_row)
- 시험: test_323_4_attach_mirror(42종 + 빈칸 · 서버 = 화면 · 옛 자리 걷힘 · 벽 한 번 · 지문) · 전수 표 ★ 둘(물넘이포장 걸친 격자 100 = 서버와 같음 · STAGE 4) · 옛 도움 파일 = 측점 → 형상 차례

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-14 17:26:53 +09:00

106 lines
4.9 KiB
Python

"""형상 얹기 한 벌(323-4) — 측점(횡단 dict) + 현황(관 · 구조물) + 구조물 표 → 그 측점 그림 자료.
구조물 열쇠(`SHAPE_KEYS` · 빈칸 경고 `blank`)를 걷고 차례대로 다시 얹음 — 관 세트 → 벽 → 포장 → 맹암거 →
빈칸 경고. 고르기 = 표 형상(322-1). 서버 상세 읽기(`_read_section_detail`) · 재생성 응답이 이것 하나를 씀.
TS 짝 = `common_util/common_util_structure_attach.ts` `attachShapes`(화면 = 초안 ?? 저장분 현황) ·
거울 시험 = `resources/tester/test_323_4_attach_mirror.py`.
"""
from __future__ import annotations
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
from B06_Section.B06_Section_Engine_Culvert import (
attach_culvert_specs,
culvert_sets_from,
pipe_points_file,
)
from B06_Section.B06_Section_Engine_Structures_Wall import attach_wall_specs, wall_specs_from
from B06_Section.B06_Section_Engine_Tilt import attach_underdrain_specs, underdrains_from
from common_util.common_util_drainage_pipes import read_pipe_points_file
from common_util.common_util_structure_catalog import catalog_row, field_name, type_ids_with
from common_util.common_util_structure_pavement import (
attach_pavement_specs,
pavement_specs_from,
)
from common_util.common_util_structure_station import (
SAME_STATION_M,
blank_keys,
plan_stations,
)
logger = logging.getLogger(__name__)
#: 측점 그림 자료 열쇠 — TS `common_util_structure_shape_keys.SHAPE_KEYS` 짝.
SHAPE_KEYS = ("culvert", "ford", "box", "ford_pavement", "revetment", "pavement", "underdrain")
def _blank_labels(type_id: str, options: dict | None, types: dict[str, Any]) -> list[str]:
"""빈 필수 칸 → 「종류 이름 칸 이름」(TS `blankLabels` 짝)."""
name = (catalog_row(type_id) or {}).get("name") or type_id
ordered = [types[type_id]] if type_id in types else []
ordered += list(types.values())
out = []
for key in blank_keys(type_id, options):
label = next(
(o.label for t in ordered for o in t.options if o.key == key and o.label), None
)
out.append(f"{name} {label or field_name(key) or key}")
return out
def _attach_blanks(sections: list[dict[str, Any]], pipes: list[Any], structures: list[Any]) -> None:
"""빈칸 경고 — 측점 계획 자리마다 덮는 관 · 구조물의 빈 필수 칸(TS `attachBlanks` 짝)."""
types = structure_type_map()
owners: dict[str, tuple[str, dict | None]] = {}
for pipe in pipes:
owner = getattr(pipe, "id", None) or f"pipe-{float(pipe.chainage_m):.2f}"
owners[owner] = (pipe.facility or "pipe", pipe.options)
for item in structures:
if item.structure_id:
owners[item.structure_id] = (item.type_id, item.options)
for station in plan_stations(pipes, structures):
if not station["blank"]:
continue
labels: list[str] = []
for owner in station["owners"]:
hit = owners.get(owner)
for label in _blank_labels(hit[0], hit[1], types) if hit else []:
if label not in labels:
labels.append(label)
for section in sections:
at = section.get("chainage_m")
if isinstance(at, (int, float)) and abs(at - station["chainage_m"]) < SAME_STATION_M:
section["blank"] = labels
def attach_shapes(sections: list[dict[str, Any]], pipes: list[Any], structures: list[Any]) -> None:
"""측점마다 그림 자료를 걷고 현황으로 다시 얹음(TS `attachShapes` 짝 · 제자리 고침)."""
for section in sections:
for key in (*SHAPE_KEYS, "blank"):
section.pop(key, None)
attach_culvert_specs(sections, culvert_sets_from(pipes))
# 관 세트가 선 측점은 벽을 안 얹음(`attach_wall_specs`) — 차례 = 화면과 같음
attach_wall_specs(sections, wall_specs_from(structures))
attach_pavement_specs(
sections,
pavement_specs_from([s.model_dump() for s in structures], type_ids_with(shape="pavement")),
)
attach_underdrain_specs(sections, underdrains_from(structures))
_attach_blanks(sections, pipes, structures)
def attach_saved_shapes(project_root: Path, sections: list[dict[str, Any]]) -> None:
"""저장분 현황(관 지점 · 구조물 정본)으로 얹음 — 구조물 정본을 못 읽으면 관만(횡단 조회는 이어 감)."""
pipes = read_pipe_points_file(pipe_points_file(project_root))
try:
structures = load_structures(str(project_root))[1]
except Exception: # noqa: BLE001 — 정본을 못 읽어도 횡단 조회는 이어 간다
logger.exception("B06 구조물 정본을 읽지 못했습니다 (관만 얹음)")
structures = []
attach_shapes(sections, pipes, structures)