- 새 B07_DesignDetail_Engine_Cad_CulvertNote = TS culvertNoteEntities · stationNoLabel 짝(두 파일 머리 표시) - 횡단 장 만들 때 CAD 칸 없음 · 옛 글 없음 · 배수관 주인 측점 = 정본 pipe_points 로 미리 넣음 칸을 얹은 사본 - 지운 칸(빈 엔티티) · 옛 글 · 설계 없음 · 이웃 측점 = 그대로 - 시험 test_260_note_cad 거울 9쌍(같은 입력 → 같은 엔티티) · 정본 읽기 · B07 그림 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL
232 lines
8.5 KiB
Python
232 lines
8.5 KiB
Python
"""B07 횡단도 표 위 CAD 칸 — 횡단배수 미리 넣음(260-4 · 요구 162-21).
|
|
|
|
⚠ 짝(①) = `B06_Section/B06_Section_UI_Cross_Detail_Cad.ts` `culvertNoteEntities` ·
|
|
`stationNoLabel` — 같은 입력 → 같은 엔티티(id 만 다름) · 거울 시험 `test_260_note_cad.py`.
|
|
B06 [상세] 모달을 한 번도 안 연 측점(칸 = 없음)에 B07 이 같은 그림을 놓음 · 사용자가 지운 칸
|
|
(빈 엔티티)은 덮지 않음 · 옛 글(`structure_note`)이 있으면 옛 글이 먼저
|
|
(모달이 처음 열 때 옮기는 차례와 같음).
|
|
좌표 = 도면 mm(글 2.0 · 줄 3.0) · 칸 왼쪽 위 = (0, 0) · 아래로 음수.
|
|
"""
|
|
|
|
import json
|
|
import math
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from B06_Section.B06_Section_Engine_Culvert import _culvert_set, pipe_points_file
|
|
from common_util.common_util_drainage_pipes import PIPE_FACILITY_PIPE, parse_pipe_points
|
|
|
|
# TS `PIPE_MATCH_M`(`design_selection`) — 관 자리와 측점이 같은 자리로 보는 폭
|
|
PIPE_MATCH_M = 0.51
|
|
_LAYER_ID = "기본"
|
|
_FONT = 2.0
|
|
_LINE = 3.0
|
|
_COLOR = "#ff7f7f"
|
|
_BASIN_FORMS = {"I": "I형", "L": "L형", "U": "ㄷ형"}
|
|
|
|
|
|
def _js_round(value: float) -> int:
|
|
"""JS `Math.round` — .5 는 위로."""
|
|
return math.floor(value + 0.5)
|
|
|
|
|
|
def _one(value: Any) -> str:
|
|
ok = isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value)
|
|
return f"{value:.1f}" if ok else ""
|
|
|
|
|
|
def _metres(value: Any) -> str:
|
|
return f"{_one(value)}m" if _one(value) else ""
|
|
|
|
|
|
def _trim(value: float) -> str:
|
|
rounded = _js_round(value * 100) / 100
|
|
return str(int(rounded)) if rounded == int(rounded) else str(rounded)
|
|
|
|
|
|
def _bond(form: Any) -> str:
|
|
"""돌쌓기(찰) · 찰붙임 → 찰 · (메) · 메붙임 → 메 · 그 밖 = 그대로."""
|
|
text = str(form) if form is not None else ""
|
|
return "찰" if "찰" in text else "메" if "메" in text else text
|
|
|
|
|
|
def _side_line(side: Any, title: str) -> str | None:
|
|
if not isinstance(side, dict) or not side.get("structure"):
|
|
return None
|
|
basin = side["structure"] == "집수정"
|
|
form = (
|
|
_BASIN_FORMS.get(side.get("basin_form") or "", "")
|
|
if basin
|
|
else _bond(side.get("revet_form"))
|
|
)
|
|
height = side.get("basin_height_m" if basin else "revet_height_m")
|
|
length = side.get("basin_length_m" if basin else "revet_length_m")
|
|
spec = ",".join(part for part in (form, f"H={_one(height)}") if part)
|
|
return f"관보호공({title}) {side['structure']}({spec}) L={_metres(length)}"
|
|
|
|
|
|
def _area(value: Any) -> float:
|
|
"""JS `Number()` 꼴 — 빈 글 = 0 · 못 읽음 = nan."""
|
|
if value is None:
|
|
return math.nan
|
|
if isinstance(value, str) and not value.strip():
|
|
return 0.0
|
|
try:
|
|
return float(value)
|
|
except (TypeError, ValueError):
|
|
return math.nan
|
|
|
|
|
|
def _chute_line(options: dict[str, Any], fallback: str | None) -> str | None:
|
|
picked = sorted(
|
|
int(hit.group(1))
|
|
for key, value in options.items()
|
|
if (hit := re.match(r"^chute_add_(\d+)_structure$", key))
|
|
and str(value if value is not None else "").strip()
|
|
)
|
|
head = f"chute_add_{picked[0]}_" if picked else None
|
|
structure = str(options[f"{head}structure"]).strip() if head else fallback
|
|
if not structure:
|
|
return None
|
|
bond_raw = options.get(f"{head}bond") if head else None
|
|
bond = _bond(bond_raw if bond_raw is not None else "") if head else ""
|
|
area = _area(options.get(f"{head}area")) if head else math.nan
|
|
spec = ",".join(part for part in (structure, bond) if part)
|
|
return f"관유출부보강({spec}) {_trim(area) if area > 0 else ''}m2"
|
|
|
|
|
|
def station_no_label(chainage_m: float, interval_m: float) -> str:
|
|
"""측점 이름(대문자 NO · 거리 정수) — TS `stationNoLabel` 짝."""
|
|
safe = interval_m if interval_m > 0 else 1.0
|
|
number = math.floor((chainage_m + 1e-6) / safe)
|
|
rest = chainage_m - number * safe
|
|
if rest >= safe - 0.05:
|
|
number += 1
|
|
rest = 0.0
|
|
return f"NO.{number}" if abs(rest) < 0.05 else f"NO.{number}+{_js_round(rest)}"
|
|
|
|
|
|
def _shape(index: int, kind: str, shape_data: dict[str, Any]) -> dict[str, Any]:
|
|
return {
|
|
"id": f"culvert-note:{index}",
|
|
"type": kind,
|
|
"lineColor": _COLOR,
|
|
"lineWidth": 1,
|
|
"layerId": _LAYER_ID,
|
|
"shapeData": shape_data,
|
|
}
|
|
|
|
|
|
def _text(index: int, label: str, x: float, y: float, align: str = "left") -> dict[str, Any]:
|
|
return _shape(
|
|
index,
|
|
"Text",
|
|
{
|
|
"label": label,
|
|
"basePoint": {"x": x, "y": y},
|
|
"options": {
|
|
"textDirection": {"x": 1, "y": 0},
|
|
"textAlign": align,
|
|
"textColor": _COLOR,
|
|
"fontSize": _FONT,
|
|
"fontFamily": "sans-serif",
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
def culvert_note_entities(
|
|
chainage_m: float,
|
|
culvert: dict[str, Any] | None,
|
|
design: dict[str, Any] | None,
|
|
station: str,
|
|
options: dict[str, Any] | None = None,
|
|
) -> list[dict[str, Any]]:
|
|
"""횡단배수 미리 넣음 — 이 측점에 놓인(주인) 배수관만 · 상자형 · 숨은 관 · 이웃 = 빈 목록.
|
|
|
|
`culvert` = 측점에 얹힌 세트(`attach_culvert_sets` 꼴) · `options` = 그 관의 `pipe_points` 옵션.
|
|
"""
|
|
if not isinstance(culvert, dict) or culvert.get("type") != "pipe":
|
|
return []
|
|
if culvert.get("body") or culvert.get("hidden_pipe"):
|
|
return []
|
|
owner = culvert.get("chainage_m")
|
|
if abs((owner if isinstance(owner, (int, float)) else chainage_m) - chainage_m) >= PIPE_MATCH_M:
|
|
return []
|
|
cx, cy, radius = 6.5, -9.0, 6.5
|
|
length = design.get("pipe_length_m") if isinstance(design, dict) else None
|
|
inside = [
|
|
"배수관",
|
|
f"Φ{_js_round(float(culvert.get('diameter_m') or 0.0) * 1000)}m/m",
|
|
f"L={_metres(length)}",
|
|
]
|
|
chute = culvert.get("chute") if isinstance(culvert.get("chute"), dict) else {}
|
|
right = [
|
|
line
|
|
for line in (
|
|
station,
|
|
_side_line(culvert.get("inlet"), "유입"),
|
|
_side_line(culvert.get("outlet"), "유출"),
|
|
_chute_line(options or {}, chute.get("structure")),
|
|
)
|
|
if line
|
|
]
|
|
entities = [
|
|
_shape(0, "Circle", {"center": {"x": cx, "y": cy}, "radius": radius}),
|
|
_shape(
|
|
1,
|
|
"Line",
|
|
{
|
|
"startPoint": {"x": cx, "y": cy - radius},
|
|
"endPoint": {"x": cx, "y": cy - radius - _LINE},
|
|
},
|
|
),
|
|
]
|
|
entities += [
|
|
_text(2 + i, line, cx, cy + _LINE * (1 - i), "center") for i, line in enumerate(inside)
|
|
]
|
|
entities += [_text(5 + i, line, 15, -_LINE * (i + 0.5)) for i, line in enumerate(right)]
|
|
return entities
|
|
|
|
|
|
def load_pipe_notes(project_root: Path) -> list[tuple[float, dict[str, Any], dict[str, Any]]]:
|
|
"""정본 `pipe_points.json` 의 배수관 — (관 누가거리, 세트, 옵션) · 관경 안 고름 = 관 없음."""
|
|
path = pipe_points_file(project_root)
|
|
try:
|
|
document = json.loads(path.read_text(encoding="utf-8"))
|
|
except (OSError, json.JSONDecodeError):
|
|
return []
|
|
pipes = []
|
|
for point in parse_pipe_points(document.get("points")):
|
|
options = dict(point.options or {})
|
|
if point.facility != PIPE_FACILITY_PIPE or options.get("pipe_diameter_mm") in (None, ""):
|
|
continue
|
|
spec = {**_culvert_set(options), "chainage_m": float(point.chainage_m)}
|
|
pipes.append((float(point.chainage_m), spec, options))
|
|
return pipes
|
|
|
|
|
|
def with_culvert_note(
|
|
design: dict[str, Any] | None,
|
|
chainage_m: float,
|
|
pipes: list[tuple[float, dict[str, Any], dict[str, Any]]],
|
|
station: str,
|
|
) -> dict[str, Any] | None:
|
|
"""CAD 칸이 없고 옛 글도 없는 주인 측점 — 미리 넣음을 칸으로 얹은 사본 · 그 밖 = 그대로.
|
|
|
|
설계가 없는 측점(dict 아님)은 그대로 — 표 · 계획선 셈이 설계 없음으로 가는 길을 안 바꿈.
|
|
"""
|
|
if not isinstance(design, dict) or design.get("structure_note_cad") is not None:
|
|
return design
|
|
if str(design.get("structure_note") or "").strip():
|
|
return design
|
|
near = [pipe for pipe in pipes if abs(pipe[0] - chainage_m) < PIPE_MATCH_M]
|
|
if not near:
|
|
return design
|
|
_at, spec, options = min(near, key=lambda pipe: abs(pipe[0] - chainage_m))
|
|
entities = culvert_note_entities(chainage_m, spec, design, station, options)
|
|
if not entities:
|
|
return design
|
|
return {**design, "structure_note_cad": {"entities": entities}}
|