diff --git a/A00_Common/design/design_pipe_ids.ts b/A00_Common/design/design_pipe_ids.ts index 1c8a2cf10..05228a613 100644 --- a/A00_Common/design/design_pipe_ids.ts +++ b/A00_Common/design/design_pipe_ids.ts @@ -129,3 +129,17 @@ export function followMovedPipe(projectId: string | null, fromM: number, toM: nu { partKey: picked.partKey }, ); } + +/** 종단 측점 하나를 지움(513-3 · 우클릭 「배관 · 구조물 삭제」 종단 · 평면 공용) — 측점 id = 주인 · `주인:역할` + * (`plannedIrregular`) · 관 기준 측점 = 그 자리 · 관 구간 끝 = 관 자리 · 그 밖 = 구조물 id(좌측 칸 [삭제]와 같은 길). */ +export function removeStationOwner( + station: { id: string; chainage_m: number }, + pipe: boolean, + remove: { pipe: (chainageM: number) => void; structure: (structureId: string) => void }, +): void { + if (pipe) return remove.pipe(station.chainage_m); + const owner = station.id.replace(/:(start|end)$/, ""); + const at = pipeChainageOf(owner); + if (at !== null) remove.pipe(at); + else remove.structure(owner); +} diff --git a/A00_Common/design/design_view_profile.ts b/A00_Common/design/design_view_profile.ts index bf8e88b5e..4aa105054 100644 --- a/A00_Common/design/design_view_profile.ts +++ b/A00_Common/design/design_view_profile.ts @@ -66,7 +66,7 @@ import "../../B05_Profile/B05_Profile_UI_Style.css"; import "../../B05_Profile/B05_Profile_UI_Style_Table.css"; import "../../B05_Profile/B05_Profile_UI_Style_Drainage.css"; import "../../B05_Profile/B05_Profile_UI_Style_Structures.css"; -import { pipeIdAt } from "./design_pipe_ids"; +import { pipeIdAt, removeStationOwner } from "./design_pipe_ids"; import { useDesignStore } from "./design_store"; import { pageSettingsPanelValues } from "../../B05_Profile/B05_Profile_UI_Page_Latest"; import { setPlusStations } from "../../common_util/common_util_structure_station"; @@ -194,9 +194,12 @@ export async function mountProfileView( onStructureMove: (from, to, station) => { if (isPipeStation(station)) profilePanel.drainage.movePipe(from, to); }, - onStructureRemove: (station) => { - if (isPipeStation(station)) profilePanel.drainage.removePipe(station.chainage_m); - }, + // 우클릭 삭제 — 관 = 관 지점 정본 · 그 밖 = 좌측 칸 [삭제]와 같은 길(513-3) + onStructureRemove: (station) => + removeStationOwner(station, isPipeStation(station), { + pipe: (chainage) => profilePanel.drainage.removePipe(chainage), + structure: (structureId) => panel.structures.removeById(structureId), + }), // 우클릭 [배관 추가] — 사이드 폼에 종류·측점을 실어 주고 [추가]로 확정(2026-08-18). onPipeAdd: (chainage) => panel.structures.addAt(chainage, "pipe"), onStructureAdd: (chainage, type) => diff --git a/B06_Section/B06_Section_UI_Section_View_Profile.ts b/B06_Section/B06_Section_UI_Section_View_Profile.ts index b1a9bf0b2..71b5e9c2b 100644 --- a/B06_Section/B06_Section_UI_Section_View_Profile.ts +++ b/B06_Section/B06_Section_UI_Section_View_Profile.ts @@ -24,6 +24,7 @@ import { } from "../A00_Common/design/design_selection"; import { paintMapPick } from "../A00_Common/design/design_pick_profile"; import { ESCAPE_FROM } from "../A00_Common/design/design_pick_escape"; +import { removeStationOwner } from "../A00_Common/design/design_pipe_ids"; import { plantsStation } from "../B05_Profile/B05_Profile_UI_Profile_Structures"; import { fetchLatestRoute, @@ -156,7 +157,12 @@ export function createSectionProfile( if (isPipeStation(station)) host.edit()?.movePipe(from, to); }, onStructureRemove: (station) => { - if (isPipeStation(station)) host.edit()?.removePipe(station.chainage_m); + const edit = host.edit(); + if (edit) + removeStationOwner(station, isPipeStation(station), { + pipe: edit.removePipe, + structure: edit.removeStructure, + }); }, onPipeAdd: (chainage) => host.edit()?.addPipe(chainage), onStructureAdd: (chainage, type) => diff --git a/resources/tester/helper_513_3_remove_station_owner.cjs b/resources/tester/helper_513_3_remove_station_owner.cjs new file mode 100644 index 000000000..c1fdc5d9d --- /dev/null +++ b/resources/tester/helper_513_3_remove_station_owner.cjs @@ -0,0 +1,38 @@ +/* 513-3 우클릭 「구조물 삭제」 관 아닌 구조물도(요구 327-1) — `design_pipe_ids.removeStationOwner` 를 Node 로 + * 돌려 JSON 한 줄(`test_513_3_remove_station_owner.py` 가 판정). 관 장부 · 고름은 가짜 · 주인 가르기는 진짜. + * node helper_513_3_remove_station_owner.cjs + */ +const path = require("path"); +const { makeLoader } = require("./helper_ts_stub_loader.cjs"); + +const BOOK = { next: 4, ids: [{ id: "pipe-s3", at: 55 }] }; +const STUBS = { + "../b_page_state": { readState: () => BOOK, writeState() {} }, + "./design_selection": { + PIPE_MATCH_M: 0.51, + isNear: () => false, + nearestWithin: () => null, + readSelection: () => null, + selectDesign() {}, + }, + common_util_pick_tag: { setPipeTagSource() {} }, +}; +const { loadTs } = makeLoader({ root: path.join(__dirname, "..", ".."), stubs: STUBS }); +const ids = loadTs(path.join(__dirname, "..", "..", "A00_Common", "design", "design_pipe_ids.ts")); +ids.usePipeIds("p1"); + +const calls = []; +const remove = { + pipe: (m) => calls.push(["pipe", m]), + structure: (id) => calls.push(["structure", id]), +}; +const cases = [ + [{ id: "pipe-s1", chainage_m: 40 }, true], // 관 기준 측점 + [{ id: "wall-7", chainage_m: 80 }, false], // 구조물 기준 측점 + [{ id: "wall-8:start", chainage_m: 100 }, false], // 구간 구조물 시작 + [{ id: "wall-8:end", chainage_m: 130 }, false], // 구간 구조물 끝 + [{ id: "pipe-s3:end", chainage_m: 60 }, false], // 관 구간 끝 = 장부 자리 + [{ id: "pipe-12.50:start", chainage_m: 10 }, false], // 옛 자리 이름 관 +]; +for (const [station, pipe] of cases) ids.removeStationOwner(station, pipe, remove); +process.stdout.write(JSON.stringify({ calls })); diff --git a/resources/tester/test_513_3_remove_station_owner.py b/resources/tester/test_513_3_remove_station_owner.py new file mode 100644 index 000000000..84769522c --- /dev/null +++ b/resources/tester/test_513_3_remove_station_owner.py @@ -0,0 +1,44 @@ +"""513-3 종단 · 평면 우클릭 「구조물 삭제」 관 아닌 구조물도(요구 327-1) — 측점 주인을 갈라 관 = 관 지점 정본 · +그 밖 = 좌측 칸 [삭제]와 같은 `removeById`(공용 `removeStationOwner`). + +TS 셈은 `helper_513_3_remove_station_owner.cjs`(Node · 주인 가르기는 진짜). + + ./venv/Scripts/python.exe -m pytest resources/tester/test_513_3_remove_station_owner.py -q +""" + +import json +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +HELPER = Path(__file__).with_name("helper_513_3_remove_station_owner.cjs") + + +def test_주인대로_지움() -> None: + run = subprocess.run( # noqa: S603 — 고정 실행 파일 + ["node", str(HELPER)], + capture_output=True, + text=True, + encoding="utf-8", + timeout=120, + ) + assert run.returncode == 0, run.stderr + assert json.loads(run.stdout)["calls"] == [ + ["pipe", 40], + ["structure", "wall-7"], + ["structure", "wall-8"], + ["structure", "wall-8"], + ["pipe", 55], + ["pipe", 12.5], + ] + + +def test_두_보기_배선() -> None: + view = (ROOT / "A00_Common" / "design" / "design_view_profile.ts").read_text(encoding="utf-8") + assert "removeStationOwner(station, isPipeStation(station)" in view + assert "panel.structures.removeById(structureId)" in view + cross = (ROOT / "B06_Section" / "B06_Section_UI_Section_View_Profile.ts").read_text( + encoding="utf-8" + ) + assert "removeStationOwner(station, isPipeStation(station)" in cross + assert "structure: edit.removeStructure" in cross