From c23ba2bd028ffd87391a718e4c534ed80f559fa5 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 10 Oct 2026 11:53:24 +0900 Subject: [PATCH] =?UTF-8?q?feat(b05):=203D=20=EC=B8=A1=EC=A0=90=20?= =?UTF-8?q?=EB=9D=A0=20=EB=81=9D=20=EC=A3=BC=ED=99=A9=20=EA=B5=AC=EC=8A=AC?= =?UTF-8?q?(=EC=B8=A1=EA=B5=AC=20=EB=B0=A9=ED=96=A5=20=EA=B3=A0=EB=A5=B4?= =?UTF-8?q?=EA=B8=B0)=20=C2=B7=20=ED=81=B4=EB=A6=AD=20=C2=B7=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=EA=B8=B0=20=EC=A7=80=EC=9B=80(176-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 저장 길(세션 uphill → uphill_overrides) · B06 종단 병합 함수 남김 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QND7Co43B24zK2P937rdxt --- A00_Common/design/design_view_profile.ts | 25 +--------- B05_Profile/B05_Profile_UI_Markers.ts | 49 +------------------ B05_Profile/B05_Profile_UI_Page_Stations.ts | 4 -- resources/tester/test_176_3_no_uphill_lamp.py | 36 ++++++++++++++ 4 files changed, 40 insertions(+), 74 deletions(-) create mode 100644 resources/tester/test_176_3_no_uphill_lamp.py diff --git a/A00_Common/design/design_view_profile.ts b/A00_Common/design/design_view_profile.ts index d29b60ecb..7bb08fc1e 100644 --- a/A00_Common/design/design_view_profile.ts +++ b/A00_Common/design/design_view_profile.ts @@ -206,10 +206,9 @@ export async function mountProfileView( let selectionSyncing = false; /* ── 측점 상단측(=측구 방향) 사용자 변경분 ───────────────────────────── - * solve가 자동 판정한 uphill_side를 3D 램프 클릭으로 바꾼 값. 세션에 보관했다가 - * 저장 시 uphill_overrides로 백엔드/DB(종단 정본)에 병합한다. */ + * solve가 자동 판정한 uphill_side를 사용자가 바꾼 값. 세션에 보관했다가 + * 저장 시 uphill_overrides로 백엔드/DB(종단 정본)에 병합한다(3D 구슬 입력은 176-3 에서 지움). */ const uphillOverrides = loadUphillOverrides(activeProjectId); - const uphillKey = (chainage: number): string => chainage.toFixed(3); const { syncIrregularSelection, syncBasinHighlight, selectStationOfPipe } = createSelectionSync({ stations: () => bridge.irregularStations(), @@ -356,25 +355,6 @@ export async function mountProfileView( profilePanel.setSelectedStation(stationId); syncIrregularSelection(stationId); }); - // 3D 램프 클릭 → 그 측을 상단측(측구 방향)으로 지정하고 세션 보관 + 램프 재렌더. - viewer.markers.onUphillPick((stationId, side) => { - if (!currentSectionDetail) return; - const base = currentSectionDetail.longitudinal.stations.find( - (station) => station.station_id === stationId, - ); - let chainage = base?.chainage_m; - if (chainage === undefined) { - const prefix = irregularStationId(""); - if (stationId.startsWith(prefix)) { - const id = stationId.slice(prefix.length); - chainage = bridge.irregularStations().find((entry) => entry.id === id)?.chainage_m; - } - } - if (chainage === undefined) return; - uphillOverrides.set(uphillKey(chainage), side); - saveUphillOverrides(activeProjectId, uphillOverrides); - renderStationLines(currentSectionDetail); - }); viewer.root.append(panel.viewControls); function restorePanel(next: RouteLatestResponse): void { @@ -389,7 +369,6 @@ export async function mountProfileView( routePoints: latest?.route_points ?? [], liveSamples: profilePanel.alignmentSamples(), corridorVisible, - uphillOverrides, }); viewer.renderStationLines(lines, roadWidths[panel.values().gradeClass] / 2); } diff --git a/B05_Profile/B05_Profile_UI_Markers.ts b/B05_Profile/B05_Profile_UI_Markers.ts index f115c28c2..f6f4e1918 100644 --- a/B05_Profile/B05_Profile_UI_Markers.ts +++ b/B05_Profile/B05_Profile_UI_Markers.ts @@ -32,8 +32,6 @@ export interface SectionStationMarker { center_x: number; center_y: number; center_z: number | null; - /** 상단측(등고 높은 쪽) — 램프 컬러 표시용. Page가 사용자 변경분을 반영해 넘긴다. */ - uphill_side?: "left" | "right" | null; frame: { left_xy: [number, number] }; /** 측점 종류(`bp`/`ep`/`regular`/`irregular`) — 어느 측점에 라벨을 달지 가르는 기준. */ kind?: string; @@ -54,10 +52,6 @@ const LABEL_LOD: ReadonlyArray<{ within: number; step: number }> = [ { within: Infinity, step: 5 }, ]; -// 측점 바 양 끝 원형 램프 색: 상단(등고 높은 쪽) 예상측=주황, 반대측=회색. -const UPHILL_LAMP_COLOR = 0xf97316; -const UPHILL_LAMP_INACTIVE_COLOR = 0x9ca3af; - const COLORS: Record = { bp: 0x10b981, ep: 0xef4444, @@ -124,7 +118,6 @@ export function createRouteMarkers( let changeListener: ((points: RouteDesignPoints) => void) | undefined; let selectionListener: ((point: PlacedRoutePoint | null) => void) | undefined; let stationSelectionListener: ((stationId: string | null) => void) | undefined; - let uphillPickListener: ((stationId: string, side: "left" | "right") => void) | undefined; let selectedStationId: string | null = null; function allPoints(): PlacedRoutePoint[] { @@ -423,7 +416,7 @@ export function createRouteMarkers( .map((entry) => ({ chainage_m: entry.chainage_m as number })), ); // 측점선(노란 띠)은 도로 반폭의 1.5배로 그린다 — 지형에 묻혀 짧아 보이는 문제 - // (2026-08-19 사용자 지시). 측구 방향 램프는 늘어난 선의 **끝단**에 따라붙는다. + // (2026-08-19 사용자 지시). const barHalfWidth = halfWidth * 1.5; stations.forEach((station) => { if (station.center_z === null) return; @@ -456,39 +449,12 @@ export function createRouteMarkers( bar.userData.stationId = station.station_id; stationGroup.add(bar); - // 측점 바 양 끝 원형 램프: 상단(등고 높은 쪽) 예상측 컬러, 반대측 회색. - // 클릭하면 그 측을 상단측(=측구 방향)으로 지정한다(onUphillPick). const label = stationLabelText(station, stationIntervalM); if (label) { const sprite = stationLabelSprite(label.text, modelToScene(center, bounds)); sprite.userData.stationNumber = label.number; stationLabelGroup.add(sprite); } - - (["left", "right"] as const).forEach((side, endIndex) => { - const active = station.uphill_side === side; - // 램프 절반 축소(1.1→0.55) — 측점 고를 때 가리는 문제(2026-08-06 보고). - const lamp = new THREE.Mesh( - new THREE.SphereGeometry(0.55, 14, 10), - new THREE.MeshBasicMaterial({ - color: active ? UPHILL_LAMP_COLOR : UPHILL_LAMP_INACTIVE_COLOR, - }), - ); - lamp.position.copy(points[endIndex]); - lamp.position.y += 0.3; - lamp.userData.uphillStationId = station.station_id; - lamp.userData.uphillSide = side; - stationGroup.add(lamp); - // 클릭 판정은 예전 크기 그대로 — 투명 히트 구를 겹쳐 "작게 보여도 잘 눌리게". - const lampHit = new THREE.Mesh( - new THREE.SphereGeometry(1.1, 8, 6), - new THREE.MeshBasicMaterial({ transparent: true, opacity: 0, depthWrite: false }), - ); - lampHit.position.copy(lamp.position); - lampHit.userData.uphillStationId = station.station_id; - lampHit.userData.uphillSide = side; - stationGroup.add(lampHit); - }); }); // 새로 만든 라벨에도 지금 솎기 단계를 그대로 먹인다. applyLabelStep(); @@ -527,7 +493,7 @@ export function createRouteMarkers( function selectStation(stationId: string | null): void { selectedStationId = stationId; stationGroup.children.forEach((object) => { - // 측점 띠(stationId 보유 Mesh)만 색을 바꾼다 — 램프(uphill*)는 제외. + // 측점 띠(stationId 보유 Mesh)만 색을 바꾼다. if (!(object instanceof THREE.Mesh) || typeof object.userData.stationId !== "string") return; const selected = object.userData.stationId === selectedStationId; (object.material as THREE.MeshBasicMaterial).color.set(selected ? 0xef4444 : 0xfacc15); @@ -553,14 +519,6 @@ export function createRouteMarkers( updateSelected, deleteSelected, selectObject(object: THREE.Object3D | undefined) { - // 상단측 램프 클릭: 해당 측을 측구 방향으로 지정(측점 선택보다 우선 판정). - if (typeof object?.userData.uphillStationId === "string") { - uphillPickListener?.( - object.userData.uphillStationId, - object.userData.uphillSide as "left" | "right", - ); - return; - } if (typeof object?.userData.stationId === "string") { selectStation(object.userData.stationId); selectionListener?.(null); @@ -610,9 +568,6 @@ export function createRouteMarkers( onStationSelectionChange(listener: (stationId: string | null) => void) { stationSelectionListener = listener; }, - onUphillPick(listener: (stationId: string, side: "left" | "right") => void) { - uphillPickListener = listener; - }, dispose() { disposeGroup(interactionGroup); disposeGroup(routeGroup); diff --git a/B05_Profile/B05_Profile_UI_Page_Stations.ts b/B05_Profile/B05_Profile_UI_Page_Stations.ts index b33f0762b..da83c9639 100644 --- a/B05_Profile/B05_Profile_UI_Page_Stations.ts +++ b/B05_Profile/B05_Profile_UI_Page_Stations.ts @@ -17,8 +17,6 @@ export interface StationLinesInput { liveSamples: ReadonlyArray<{ chainage_m: number; elevation_m: number }> | null; /** [예상형상] 켜짐 — 측점 바를 계획고 위로 올린다. */ corridorVisible: boolean; - /** 상단측(측구 방향) 사용자 변경분 — 키는 누가거리 3자리. */ - uphillOverrides: Map; } export function stationLinesOf(input: StationLinesInput) { @@ -60,8 +58,6 @@ export function stationLinesOf(input: StationLinesInput) { // max 로 잡으면 코리도가 절취해 내려간 노면을 두고 막대만 원지반에 떠 있다. // 코리도가 켜져 있으면(designAt 이 값을 줌) 계획고를 그대로 쓴다. center_z: design !== null && station.center_z !== null ? design : station.center_z, - uphill_side: - input.uphillOverrides.get(station.chainage_m.toFixed(3)) ?? station.uphill_side ?? null, }; }); } diff --git a/resources/tester/test_176_3_no_uphill_lamp.py b/resources/tester/test_176_3_no_uphill_lamp.py new file mode 100644 index 000000000..3f1b7a61c --- /dev/null +++ b/resources/tester/test_176_3_no_uphill_lamp.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +"""PLAN 176-3 — B05 3D 측점 노란 띠 끝 주황 구슬(측구 방향 고르기) · 클릭 · 처리기 지움. + +저장 길(세션 uphill → uphill_overrides)과 B06 이 쓰는 종단 병합 함수는 남김. +""" + +import os + +ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..") + + +def _read(*parts): + with open(os.path.join(ROOT, *parts), encoding="utf-8") as f: + return f.read() + + +def test_markers_have_no_lamp(): + src = _read("B05_Profile", "B05_Profile_UI_Markers.ts") + for gone in ("uphillStationId", "onUphillPick", "0xf97316", "UPHILL_LAMP", "lampHit"): + assert gone not in src, gone + + +def test_page_has_no_lamp_handler(): + src = _read("A00_Common", "design", "design_view_profile.ts") + assert "onUphillPick" not in src + assert "uphillOverrides" not in _read("B05_Profile", "B05_Profile_UI_Page_Stations.ts") + + +def test_save_path_and_merge_kept(): + # 저장 길(세션 → 저장 몸통)과 B06 이 부르는 종단 병합 함수는 그대로 + page = _read("A00_Common", "design", "design_view_profile.ts") + assert "persistUphillOverrides" in page + assert "uphill_overrides" in _read("B05_Profile", "B05_Profile_UI_Page_Actions.ts") + assert "def _merge_uphill_overrides_into_longitudinal" in _read( + "B05_Profile", "B05_Profile_Router_Confirm.py" + )