From a108c3ae112c131ebdf96ca829ff3e45e4a230d2 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 17 Oct 2026 17:17:51 +0900 Subject: [PATCH] =?UTF-8?q?feat(B05=20=ED=8F=89=EB=A9=B4=20=EB=8F=84?= =?UTF-8?q?=EA=B5=AC=EC=A4=84):=20=EA=B5=AC=EB=B6=84=EC=84=A0=20=C2=B7=20?= =?UTF-8?q?=EC=8A=A4=EB=83=85=20=EA=B0=84=EA=B2=A9=20=EA=B3=A0=EB=A5=B4?= =?UTF-8?q?=EA=B8=B0=20=C2=B7=20Ctrl+=ED=9C=A0=205=C2=B0=20=ED=9A=8C?= =?UTF-8?q?=EC=A0=84=20=C2=B7=20[=EA=B8=B0=EC=9A=B8=EA=B8=B0]=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=20=EA=BA=BC=EC=A7=90=20(390-1=20=C2=B7=202=20=C2=B7?= =?UTF-8?q?=203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - [측점 스냅] · 간격 | 분석 지형 구분선(분석 지형 숨으면 같이 숨김) · [기울기] 기본 끔 - 스냅 간격 0.1 · 1 · 10 m · 측점 No.(기본) — 프로젝트별 화면 기억 · 스냅 끔이면 흐림 · MC 를 간격 배수에 · 허용 오차 간격 따라(0.1 m = 0.005 m) - Ctrl+휠 한 칸 5° 회전(단추 15° 그대로 · 손가락 · 트랙패드 벌림 = 확대 · 추천 지도 안 돎) · 도움말 한 줄 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD --- A00_Common/b_page_state.ts | 2 + B05_Profile/B05_Profile_UI_RouteEdit.ts | 16 +++---- .../B05_Profile_UI_RouteEdit_Chrome.ts | 11 +++++ B05_Profile/B05_Profile_UI_RouteEdit_Input.ts | 7 ++- B05_Profile/B05_Profile_UI_RouteEdit_Marks.ts | 4 +- .../B05_Profile_UI_RouteEdit_Rotate.ts | 17 ++++++-- .../B05_Profile_UI_RouteEdit_SnapStep.ts | 40 +++++++++++++++++ .../B05_Profile_UI_Style_RouteEdit.css | 9 ++++ .../logic/B05_Profile_RouteEdit_StationFit.ts | 12 ++++-- resources/tester/helper_366_3_station_fit.cjs | 15 +++++-- .../tester/helper_b05_routeedit_view.cjs | 43 +++++++++++++++++++ resources/tester/test_366_3_station_fit.py | 15 +++++++ .../tester/test_381_2_routeedit_marks.py | 6 +-- .../tester/test_b05_routeedit_initial.py | 14 ++++-- resources/tester/test_b05_routeedit_view.py | 10 +++++ 15 files changed, 191 insertions(+), 30 deletions(-) create mode 100644 B05_Profile/B05_Profile_UI_RouteEdit_SnapStep.ts diff --git a/A00_Common/b_page_state.ts b/A00_Common/b_page_state.ts index 60ee6b9dd..8250c2cce 100644 --- a/A00_Common/b_page_state.ts +++ b/A00_Common/b_page_state.ts @@ -75,6 +75,8 @@ export const STATE_REGISTRY = { "routeedit-locks": { bucket: "pref", scope: "project" }, /** B05 계획노선 편집 창 — 분석 지형(las · sheet). 확정 지표면은 안 바꿈 */ "routeedit-terrain": { bucket: "pref", scope: "project" }, + /** B05 계획노선 편집 창 — 측점 스냅 간격(0.1 · 1 · 10 · station) */ + "routeedit-snap-step": { bucket: "pref", scope: "project" }, "masshaul-open": { bucket: "pref", scope: "global", diff --git a/B05_Profile/B05_Profile_UI_RouteEdit.ts b/B05_Profile/B05_Profile_UI_RouteEdit.ts index 540c6e57a..e74c3bf2e 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit.ts @@ -91,6 +91,7 @@ import { import type { DesignPageHost } from "../A00_Common/design/design_page"; import * as draftLink from "./B05_Profile_UI_RouteEdit_Draft"; import { bindRouteShift } from "./B05_Profile_UI_RouteEdit_Shift"; +import { bindStationSnap, paintStationSnap } from "./B05_Profile_UI_RouteEdit_SnapStep"; import * as fit from "./logic/B05_Profile_RouteEdit_StationFit"; import "./B05_Profile_UI_Style_RouteEdit.css"; @@ -173,11 +174,7 @@ export async function mountRouteEditor( curveSpans(line, curveInfo), ); }; - const setStationSnap = (on: boolean): void => { - stationSnap = on; - stationSnapButton.classList.toggle("is-active", on); - stationSnapButton.setAttribute("aria-pressed", String(on)); - }; + const setStationSnap = (on: boolean) => paintStationSnap(stationSnapButton, (stationSnap = on)); /** 되돌리기 사진첩 — 노선을 읽은 뒤에 선다(그전에는 되돌릴 것이 없다). */ let history: RouteEditHistory | null = null; let meta: VWorldMeta | null = null; @@ -264,8 +261,8 @@ export async function mountRouteEditor( const slope = createSlopeLabels({ projectId, isClosed: () => closed, onChange: () => draw() }); /** 1m 간격 등고선 켬끔 — 기본 꺼짐(계획서 18 ②). */ const showMinorContours = bindMinorContourToggle(minorContourButton, () => draw()); - stationSnapButton.addEventListener("click", () => { - setStationSnap(!stationSnap); + const snapStepM = bindStationSnap(chrome, projectId, stationIntervalM, (toggle) => { + if (toggle) setStationSnap(!stationSnap); if (stationSnap && history) applyEdit("곡선 가운데(MC)를 일반측점에 맞췄습니다."); // 전체 · 한 걸음 draw(); }); @@ -416,7 +413,7 @@ export async function mountRouteEditor( violationCount: nodeInfo.filter((node) => node.violations.length).length, minRadiusM, fresh: nodeInfo.length === 0, - offStation: stationSnap ? fit.offStationCount(plannedLine, curveInfo, stationIntervalM) : 0, + offStation: stationSnap ? fit.offStationCount(plannedLine, curveInfo, snapStepM()) : 0, }); // ── 곡선 라벨 — 고른 꺾임점 옆에 뜨는 조작 패널. 배선은 `_CurveBar` 몫 ── @@ -450,7 +447,7 @@ export async function mountRouteEditor( /** 한 번의 편집을 마무리한다 — 다시 그리고, 라벨·상태줄을 맞추고, 되돌리기에 쌓는다. * 측점 스냅 켬이면 고친 자리 앞 곡선부터 MC 를 일반측점에(366-3 · 되살림 `fitting=false` 은 안 맞춤). */ function applyEdit(message: string, record = true, fitting = true): void { - const limits = { minRadiusM, limitRadiusM, limitArcM, intervalM: stationIntervalM }; + const limits = { minRadiusM, limitRadiusM, limitArcM, intervalM: snapStepM() }; const from = fit.fitStart(history?.current?.(), snapshotNow()); if (stationSnap && fitting) planned = fit.fitCurveStations(snapshotNow(), limits, from); markEdited(); @@ -574,6 +571,7 @@ export async function mountRouteEditor( getMeta: () => meta, // 돌린 지도에서는 손이 민 방향과 그림이 움직일 방향이 다르다 — 거꾸로 돌려 넘긴다. unrotateDelta: rotation.unrotateDelta, + rotateWheel: rotation.wheel, // Ctrl+휠 = 5° 돌리기(390-3) draw, }); diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts index 6c1273f5e..faa7659c5 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts @@ -110,6 +110,16 @@ export function createRouteEditChrome( + +