feat(524-1): B07 평면도 설정 모달 정리 — 기본 항목 지형도 하나 · 양식 칸 숨김 · 종류 척도 2열 · 항목 그룹 구분선 · 대피소 사토장 자리 지움
- 기본 종류 = 지형도 하나(옛 기본 이관 더하기 지움) · 대피소 조각 · 자리 폼 · plan-marks 끝점 지움 - 모달 양식 칸 = 평면도면 숨김 · 종류 척도 영역 2열 · 항목 3열 유지 + 그룹 구분선(PIECE_GROUPS 한 곳) - 낡은 시험 수선: 기본 종류가 하나로 줄어 route · layout · landuse · watershed 기본 전제를 add_kind 로 세움 · 대피소 자리 시험 지움 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,8 @@
|
||||
plan_kinds/{종류 id}.json 종류 하나 = {name, pieces, order, layout, scale} — 파일 하나씩
|
||||
(유역도 양식은 + contour_interval_m 등고선 간격 5 · 10 m)
|
||||
plan_pieces/{장}/{조각}.json 정보 조각 하나 = 그 장 범위의 기하(사업지 좌표 m)
|
||||
plan_marks.json 사용자가 정한 대피소 · 사토장 자리
|
||||
|
||||
`plan_kinds/` 가 없으면 기본 종류(지형 · 노선배치도 · 대피소 · 라이다 · 용지도 · 배수유역도)를
|
||||
쓴다. 양식(layout) = plan(1/1,200 장 나눔 · 조각 고름) · watershed(유역도 1/6,000 한 장 · 조각
|
||||
`plan_kinds/` 가 없으면 기본 종류(지형도 하나)를 쓴다. 양식(layout) = plan(1/1,200 장 나눔 · 조각 고름) · watershed(유역도 1/6,000 한 장 · 조각
|
||||
없음). 처음 고칠 때 기본 종류를 파일로 내려 두고 그 위에서 추가 · 업데이트 · 지운다 — 기본 종류도
|
||||
고치고 지울 수 있다.
|
||||
조각은 서버가 도면을 열 때마다 원자료(B05 노선 · 곡선표 · 구조물, B06 횡단 설계, B04 배경)
|
||||
@@ -24,7 +22,6 @@ from B05_Profile.B05_Profile_Engine_Sections import (
|
||||
cross_station,
|
||||
find_cross_file,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Structures_Repository import load_structures
|
||||
from B06_Section.B06_Section_Engine_Design import compute_cross_design, curve_widening_args
|
||||
from B07_DesignDetail.B07_DesignDetail_Engine_Cad import (
|
||||
infer_station_interval,
|
||||
@@ -86,8 +83,6 @@ PLAN_KIND_ID = re.compile(r"^plan_([a-z][a-z0-9]{0,31})(?:_(\d+))?$")
|
||||
_NAME_MAX = 40
|
||||
WATERSHED_LAYOUT = "watershed"
|
||||
_LAYOUTS = ("plan", WATERSHED_LAYOUT)
|
||||
_REFUGE_TYPES = {"refuge": "대피소", "spoil_bank": "사토장"}
|
||||
_REFUGE_OFFSET_M = 8.0 # 대피소 · 사토장 기호를 중심선에서 띄우는 거리(좌측 · 1/1,200 실거리)
|
||||
|
||||
|
||||
def plan_title(name: str) -> str:
|
||||
@@ -101,8 +96,9 @@ def _kinds_dir(project_root: Path) -> Path:
|
||||
return _design_root(project_root) / "plan_kinds"
|
||||
|
||||
|
||||
# 이관 표시 `_{id}_added` — 있으면 그 기본 종류를 이미 더해 준 폴더(지운 뒤 되살리지 않음).
|
||||
_ADDED_ONCE = ("landuse", "watershed")
|
||||
def _marks_path(project_root: Path) -> Path:
|
||||
"""옛 대피소 · 사토장 자리 파일 — 읽지 않음 · [초기화]가 지우기만."""
|
||||
return _design_root(project_root) / "plan_marks.json"
|
||||
|
||||
|
||||
def _scale(layout: str, value: Any) -> int:
|
||||
@@ -132,27 +128,6 @@ def _document(kind: dict[str, Any], order: int) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def _add_defaults_once(folder: Path) -> None:
|
||||
"""기본 종류가 늘기 전에 종류 파일을 내린 프로젝트 — 늘어난 기본 종류를 한 번 더한다."""
|
||||
pending = [kind_id for kind_id in _ADDED_ONCE if not (folder / f"_{kind_id}_added").exists()]
|
||||
if not pending:
|
||||
return
|
||||
documents = []
|
||||
for path in folder.glob("*.json"):
|
||||
try:
|
||||
documents.append(_read_json(path))
|
||||
except (OSError, ValueError):
|
||||
continue
|
||||
names = {str(document.get("name")) for document in documents}
|
||||
order = max((int(document.get("order") or 0) for document in documents), default=-1) + 1
|
||||
for kind_id in pending:
|
||||
kind = next(kind for kind in DEFAULT_KINDS if kind["id"] == kind_id)
|
||||
if not (folder / f"{kind_id}.json").exists() and kind["name"] not in names:
|
||||
atomic_write_json(folder / f"{kind_id}.json", _document(kind, order))
|
||||
order += 1
|
||||
(folder / f"_{kind_id}_added").touch()
|
||||
|
||||
|
||||
def list_kinds(project_root: Path) -> list[dict[str, Any]]:
|
||||
"""종류 목록 [{id, name, pieces, layout, scale}] — 차례대로. 파일이 없으면 기본 종류."""
|
||||
folder = _kinds_dir(project_root)
|
||||
@@ -168,7 +143,6 @@ def list_kinds(project_root: Path) -> list[dict[str, Any]]:
|
||||
}
|
||||
for kind in DEFAULT_KINDS
|
||||
]
|
||||
_add_defaults_once(folder)
|
||||
rows: list[tuple[int, str, dict[str, Any]]] = []
|
||||
for path in folder.glob("*.json"):
|
||||
if not KIND_ID.fullmatch(path.stem):
|
||||
@@ -206,8 +180,6 @@ def _materialize(project_root: Path) -> list[dict[str, Any]]:
|
||||
if not folder.is_dir():
|
||||
for order, kind in enumerate(kinds):
|
||||
atomic_write_json(folder / f"{kind['id']}.json", _document(kind, order))
|
||||
for kind_id in _ADDED_ONCE:
|
||||
(folder / f"_{kind_id}_added").touch()
|
||||
return kinds
|
||||
|
||||
|
||||
@@ -350,40 +322,6 @@ def delete_kind(project_root: Path, kind_id: str) -> None:
|
||||
path.unlink()
|
||||
|
||||
|
||||
# ── 대피소 · 사토장 자리(사용자 지정) ─────────────────
|
||||
|
||||
|
||||
def _marks_path(project_root: Path) -> Path:
|
||||
return _design_root(project_root) / "plan_marks.json"
|
||||
|
||||
|
||||
def read_marks(project_root: Path) -> list[dict[str, Any]]:
|
||||
path = _marks_path(project_root)
|
||||
if not path.is_file():
|
||||
return []
|
||||
try:
|
||||
marks = _read_json(path).get("marks")
|
||||
except (OSError, ValueError):
|
||||
return []
|
||||
return [mark for mark in marks or [] if isinstance(mark, dict)]
|
||||
|
||||
|
||||
def write_marks(project_root: Path, marks: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
clean: list[dict[str, Any]] = []
|
||||
for mark in marks:
|
||||
chainage = mark.get("chainage_m")
|
||||
if not isinstance(chainage, (int, float)) or chainage < 0:
|
||||
raise ValueError("대피소 · 사토장 자리의 측점(m)이 올바르지 않습니다.")
|
||||
refuge, spoil = bool(mark.get("refuge")), bool(mark.get("spoil"))
|
||||
if refuge or spoil:
|
||||
clean.append(
|
||||
{"chainage_m": round(float(chainage), 2), "refuge": refuge, "spoil": spoil}
|
||||
)
|
||||
clean.sort(key=lambda mark: mark["chainage_m"])
|
||||
atomic_write_json(_marks_path(project_root), {"marks": clean})
|
||||
return clean
|
||||
|
||||
|
||||
# ── 정보 조각 ─────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -457,50 +395,6 @@ def slope_records(
|
||||
return records
|
||||
|
||||
|
||||
def _refuge_items(
|
||||
project_root: Path,
|
||||
route: list[tuple[float, float, float]],
|
||||
start_m: float,
|
||||
end_m: float,
|
||||
scale: int = DRAWING_SCALE_PLAN,
|
||||
) -> dict[str, Any]:
|
||||
"""대피소 · 사토장 = 구조물 정본의 그 종류 + 사용자가 정한 자리. 한 자리에 둘 다 가능.
|
||||
|
||||
띄움은 종이 거리가 같게 축척 따라(141-2)."""
|
||||
offset = _REFUGE_OFFSET_M * scale / DRAWING_SCALE_PLAN
|
||||
spots: dict[float, set[str]] = {}
|
||||
_revision, structures = load_structures(str(project_root))
|
||||
for structure in structures:
|
||||
label = _REFUGE_TYPES.get(structure.type_id)
|
||||
if not label:
|
||||
continue
|
||||
chainage = structure.chainage_m
|
||||
if chainage is None and structure.start_m is not None and structure.end_m is not None:
|
||||
chainage = (structure.start_m + structure.end_m) / 2.0
|
||||
if chainage is not None:
|
||||
spots.setdefault(round(float(chainage), 1), set()).add(label)
|
||||
for mark in read_marks(project_root):
|
||||
key = round(float(mark["chainage_m"]), 1)
|
||||
if mark.get("refuge"):
|
||||
spots.setdefault(key, set()).add("대피소")
|
||||
if mark.get("spoil"):
|
||||
spots.setdefault(key, set()).add("사토장")
|
||||
items = []
|
||||
for chainage, labels in sorted(spots.items()):
|
||||
if not (start_m <= chainage <= end_m) or len(route) < 2:
|
||||
continue
|
||||
x, y, nx, ny = route_frame(route, chainage)
|
||||
items.append(
|
||||
{
|
||||
"chainage_m": chainage,
|
||||
"label": " · ".join(name for name in ("대피소", "사토장") if name in labels),
|
||||
"x": x + nx * offset,
|
||||
"y": y + ny * offset,
|
||||
}
|
||||
)
|
||||
return {"items": items}
|
||||
|
||||
|
||||
def build_pieces(
|
||||
context: Any,
|
||||
longitudinal: dict[str, Any],
|
||||
@@ -582,8 +476,6 @@ def build_pieces(
|
||||
label += f" L={length:.1f}m"
|
||||
items.append({"label": label, "x": x, "y": y, "nx": nx, "ny": ny})
|
||||
pieces["endpoints"] = {"items": items}
|
||||
if "refuge" in wanted:
|
||||
pieces["refuge"] = _refuge_items(project_root, route, start_m, end_m, scale)
|
||||
if "drainage" in wanted:
|
||||
min_x, min_y, max_x, max_y = box
|
||||
pieces["drainage"] = {
|
||||
|
||||
Reference in New Issue
Block a user