- 성토사면 외곽선 → 노견선 · 성토 외곽선 · 절토 외곽선 셋 · 배수시설 기호 조각 신설 · 라이다 음영
- 기본 넷 처음 체크 = 지금 그림(배수시설 기호는 빼 둠) · 옛 종류 파일 slope_outline 은 셋으로 펼침
- PUT /plan-kinds/{id} — 이름 · 조각 고침(기본 넷 포함) · 그 종류 확정본 풀림
- GET /plan-kinds 에 측점 간격 실음 — 측점 NO + 거리 입력용
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPJGtfXo6GMbcpiboQC4uK
127 lines
4.6 KiB
Python
127 lines
4.6 KiB
Python
"""B07 계획평면도 종류 · 대피소 자리 라우터 (PLAN 60장).
|
|
|
|
종류 = 정보 조각 고르기 목록. 추가 · 업데이트 · 지우기는 파일 하나씩(`plan_kinds/{id}.json`)이다.
|
|
대피소 · 사토장 자리는 구조물 정본 밖에서 사용자가 더 정한 자리(`plan_marks.json`)다.
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter
|
|
from fastapi.responses import JSONResponse
|
|
from pydantic import BaseModel, Field
|
|
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import infer_station_interval
|
|
from B07_DesignDetail.B07_DesignDetail_Engine_Cad_PlanKinds import PIECES
|
|
from B07_DesignDetail.B07_DesignDetail_Router import _confirmed_source
|
|
from B07_DesignDetail.B07_DesignDetail_Router_Frame import _project_root
|
|
from B07_DesignDetail.B07_DesignDetail_Router_Support_Io import _read_json
|
|
from B07_DesignDetail.B07_DesignDetail_Router_Support_Plan import (
|
|
add_kind,
|
|
delete_kind,
|
|
list_kinds,
|
|
read_marks,
|
|
update_kind,
|
|
write_marks,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/api/projects", tags=["B07 Design Detail"])
|
|
|
|
|
|
class PlanKindRequest(BaseModel):
|
|
name: str
|
|
pieces: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class PlanMark(BaseModel):
|
|
chainage_m: float = Field(ge=0)
|
|
refuge: bool = False
|
|
spoil: bool = False
|
|
|
|
|
|
class PlanMarksRequest(BaseModel):
|
|
marks: list[PlanMark] = Field(default_factory=list)
|
|
|
|
|
|
def _state(root: Any) -> dict[str, Any]:
|
|
return {
|
|
"status": "success",
|
|
"pieces": [{"id": piece, "name": name} for piece, name, _locked in PIECES],
|
|
"kinds": list_kinds(root),
|
|
"marks": read_marks(root),
|
|
}
|
|
|
|
|
|
def _error(status: int, exc: Exception) -> JSONResponse:
|
|
return JSONResponse(status_code=status, content={"status": "error", "message": str(exc)})
|
|
|
|
|
|
async def _station_interval(project_id: UUID) -> float | None:
|
|
"""종단 측점 간격(m) — 측점 NO + 거리 입력을 누가거리로 바꾸는 기준. 종단이 없으면 None."""
|
|
try:
|
|
_route_id, _root, longitudinal_path, _bypassed = await _confirmed_source(project_id)
|
|
longitudinal = await asyncio.to_thread(_read_json, longitudinal_path)
|
|
except (FileNotFoundError, PermissionError, OSError, ValueError):
|
|
return None
|
|
return infer_station_interval(longitudinal.get("stations") or [])
|
|
|
|
|
|
@router.get("/{project_id}/plan-kinds")
|
|
async def get_plan_kinds(project_id: UUID) -> Any:
|
|
"""고를 수 있는 조각 · 지금 종류 · 대피소 자리 · 측점 간격."""
|
|
root = await _project_root(project_id)
|
|
state = await asyncio.to_thread(_state, root)
|
|
return {**state, "station_interval_m": await _station_interval(project_id)}
|
|
|
|
|
|
@router.post("/{project_id}/plan-kinds")
|
|
async def post_plan_kind(project_id: UUID, request: PlanKindRequest) -> Any:
|
|
"""종류 추가 — 조각을 골라 이름을 붙인다."""
|
|
try:
|
|
root = await _project_root(project_id)
|
|
await asyncio.to_thread(add_kind, root, request.name, request.pieces)
|
|
return await asyncio.to_thread(_state, root)
|
|
except ValueError as exc:
|
|
return _error(400, exc)
|
|
|
|
|
|
@router.put("/{project_id}/plan-kinds/{kind_id}")
|
|
async def put_plan_kind(project_id: UUID, kind_id: str, request: PlanKindRequest) -> Any:
|
|
"""종류 업데이트 — 이름 · 조각을 고친다(기본 넷도)."""
|
|
try:
|
|
root = await _project_root(project_id)
|
|
await asyncio.to_thread(update_kind, root, kind_id, request.name, request.pieces)
|
|
return await asyncio.to_thread(_state, root)
|
|
except ValueError as exc:
|
|
return _error(400, exc)
|
|
except FileNotFoundError as exc:
|
|
return _error(404, exc)
|
|
|
|
|
|
@router.delete("/{project_id}/plan-kinds/{kind_id}")
|
|
async def delete_plan_kind(project_id: UUID, kind_id: str) -> Any:
|
|
"""종류 지우기 — 기본 넷도 지울 수 있다."""
|
|
try:
|
|
root = await _project_root(project_id)
|
|
await asyncio.to_thread(delete_kind, root, kind_id)
|
|
return await asyncio.to_thread(_state, root)
|
|
except ValueError as exc:
|
|
return _error(400, exc)
|
|
except FileNotFoundError as exc:
|
|
return _error(404, exc)
|
|
|
|
|
|
@router.put("/{project_id}/plan-marks")
|
|
async def put_plan_marks(project_id: UUID, request: PlanMarksRequest) -> Any:
|
|
"""사용자가 정한 대피소 · 사토장 자리를 통째로 갈아 쓴다."""
|
|
try:
|
|
root = await _project_root(project_id)
|
|
marks = [mark.model_dump() for mark in request.marks]
|
|
await asyncio.to_thread(write_marks, root, marks)
|
|
return await asyncio.to_thread(_state, root)
|
|
except ValueError as exc:
|
|
return _error(400, exc)
|