From 767adc4a4202b2574ebd9cc80a6018c20fcc19ec Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 30 Sep 2026 00:00:21 +0900 Subject: [PATCH] =?UTF-8?q?fix(B06):=20=EC=B4=88=EC=95=88=20=EB=90=98?= =?UTF-8?q?=EC=96=B9=EA=B8=B0=EB=8A=94=20=EC=98=A8=20=EC=B9=B8=EB=A7=8C=20?= =?UTF-8?q?=C2=B7=20=EC=A1=B0=EC=A0=95=EA=B0=92=20=EC=93=B0=EA=B8=B0?= =?UTF-8?q?=EB=8A=94=20=EC=A0=80=EC=9E=A5=EA=B0=92=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=B6=9C=EB=B0=9C(PLAN=2024-4a)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sub8 24-7a 되얹기(일부 칸)와 폼 「칸 전부」 규칙을 가름 — 되얹기는 안 온 칸 · 기슭막이 높이 · 형태를 건드리지 않음 - 기슭막이 조작값 쓰기가 세션이 없을 때 저장된 좌우 · 상하에서 출발 — 새 탭에서 폼 칸 하나만 고쳐도 저장값이 지워지던 것 - 시험 test_b06_adjust_single_form 7건(되얹기 1 추가) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU --- B06_Section/B06_Section_UI_Page_Pipe_Options.ts | 13 ++++++++++--- B06_Section/B06_Section_UI_Page_Station_Controls.ts | 5 ++++- resources/tester/helper_b06_adjust_single_form.cjs | 11 +++++++++-- resources/tester/test_b06_adjust_single_form.py | 10 ++++++++++ 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/B06_Section/B06_Section_UI_Page_Pipe_Options.ts b/B06_Section/B06_Section_UI_Page_Pipe_Options.ts index a452e0fe..73970a19 100644 --- a/B06_Section/B06_Section_UI_Page_Pipe_Options.ts +++ b/B06_Section/B06_Section_UI_Page_Pipe_Options.ts @@ -33,11 +33,14 @@ export interface PipeOptionsContext { * 덮는 측점 카드를 다시 그린다 — 조정창 조작과 같은 흐름이라 도면이 바로 따라온다 * (2026-08-29 사용자 보고: 값만 바뀌고 횡단도가 그대로였다). * 여기서는 화면 캐시만 고친다 — 영구저장은 [저장]·[확정] 몫이다. + * `whole` — 폼이 보낸 **칸 전부**(비운 칸 = 지움 · 기슭막이 높이 · 형태 없음 = 자동 · 24-4a). + * 초안 되얹기처럼 일부만 오면 false — 온 칸만 얹는다. */ export function applyPipeOptionsToCache( ctx: PipeOptionsContext, chainageM: number, patch: Record, + whole = true, ): void { const owner = ctx .detail() @@ -66,6 +69,7 @@ export function applyPipeOptionsToCache( }; /** 폼 칸 값 — 폼이 보낸 것이 칸 전부라 비운 칸은 지운다(계획서 24-4a 빈 값). */ const own = (target: T, field: keyof T, value: unknown): void => { + if (value === undefined && !whole) return; if (value === undefined) delete (target as Record)[field as string]; else (target as Record)[field as string] = value; }; @@ -98,9 +102,12 @@ export function applyPipeOptionsToCache( ] as const) { const height = num(`${prefix}_revet_height_m`); const form = patch[`${prefix}_revet_form`]; + if (!whole && height === undefined && typeof form !== "string") continue; + // 빈 칸 = 자동(null) · 일부만 온 되얹기는 안 온 칸을 그대로(undefined). + const blank = whole ? null : undefined; ctx.revetOffset.update(owner.chainage_m, role, { - h: height ?? null, - m: typeof form === "string" ? form : null, + h: height ?? blank, + m: typeof form === "string" ? form : blank, }); // 형태마다 높이 한계가 있다(돌쌓기(메) 2.0m 등). 기하가 잘라 낸 실제 높이를 // 폼에 되돌린다 — 숫자만 커지고 그림은 그대로인 상태를 남기지 않는다. @@ -156,6 +163,6 @@ export function replayDraftPipeOptions( const patch = Object.fromEntries( Object.entries(pipe.options ?? {}).filter(([key]) => !ADJUST_OWNED.test(key)), ); - if (Object.keys(patch).length) applyPipeOptionsToCache(ctx, pipe.chainage_m, patch); + if (Object.keys(patch).length) applyPipeOptionsToCache(ctx, pipe.chainage_m, patch, false); } } diff --git a/B06_Section/B06_Section_UI_Page_Station_Controls.ts b/B06_Section/B06_Section_UI_Page_Station_Controls.ts index fdf9767b..f3412564 100644 --- a/B06_Section/B06_Section_UI_Page_Station_Controls.ts +++ b/B06_Section/B06_Section_UI_Page_Station_Controls.ts @@ -272,7 +272,10 @@ export function createStationControls(deps: StationControlDeps): StationControls }, update: (chainageM, role, patch) => { const key = revetKey(chainageM, role); - const current = revetShifts.get(key) ?? { ...ZERO_ADJUST }; + // 세션이 없으면 **저장된 조작값**에서 출발한다 — 자동(0)에서 출발하면 폼 칸 하나만 고쳐도 + // 저장해 둔 좌우 · 상하가 지워진다(계획서 24-4a). + const saved = sectionAt(chainageM)?.design?.revet_adjust?.[role]; + const current = revetShifts.get(key) ?? { ...ZERO_ADJUST, ...saved }; const next: WallAdjust = { x: clampMove(patch.x ?? current.x), // d: null = 기본 자리(배관 벽 — 경사길이 4m 지점) 유지. diff --git a/resources/tester/helper_b06_adjust_single_form.cjs b/resources/tester/helper_b06_adjust_single_form.cjs index 294862ae..726b0e7a 100644 --- a/resources/tester/helper_b06_adjust_single_form.cjs +++ b/resources/tester/helper_b06_adjust_single_form.cjs @@ -242,11 +242,18 @@ const ctx = { applyPipeOptionsToCache(ctx, 100, { outlet_revet_height_m: 2.5, outlet_revet_form: "돌쌓기(메)" }); const filled = updates.splice(0); applyPipeOptionsToCache(ctx, 100, {}); +// 초안 되얹기(24-7a)는 일부 칸만 — 안 온 칸 · 기슭막이 높이 · 형태를 건드리지 않는다. +const clearedSpec = { outlet: { ...section.culvert.outlet }, inlet: { ...section.culvert.inlet } }; +section.culvert.outlet = { revet_height_m: 3, revet_form: "콘크리트" }; +const clearedUpdates = updates.splice(0); +applyPipeOptionsToCache(ctx, 100, { outlet_revet_length_m: 12 }, false); +out.replay = { updates: updates.splice(0), outletSpec: { ...section.culvert.outlet } }; +updates.push(...clearedUpdates); out.cache = { filled, cleared: updates, - outletSpec: section.culvert.outlet, - inletSpec: section.culvert.inlet, + outletSpec: clearedSpec.outlet, + inletSpec: clearedSpec.inlet, }; process.stdout.write(JSON.stringify(out)); diff --git a/resources/tester/test_b06_adjust_single_form.py b/resources/tester/test_b06_adjust_single_form.py index ab647100..0935a1dc 100644 --- a/resources/tester/test_b06_adjust_single_form.py +++ b/resources/tester/test_b06_adjust_single_form.py @@ -67,6 +67,16 @@ def test_폼_값이_그림으로_가고_비운_칸은_자동(out) -> None: assert cache["outletSpec"] == {} and cache["inletSpec"] == {} +def test_초안_되얹기는_온_칸만(out) -> None: + """24-7a 되얹기는 일부 칸만 보낸다 — 높이 · 형태(조정값)와 안 온 칸은 그대로.""" + assert out["replay"]["updates"] == [] + assert out["replay"]["outletSpec"] == { + "revet_height_m": 3, + "revet_form": "콘크리트", + "revet_length_m": 12, + } + + def test_사본_줄은_모두_폼_칸으로_옮겨_붙는다() -> None: """사본이 만드는 줄 종류가 옮김 목록에 다 있어야 [횡단 조정] 상자 없이도 보인다.""" dock = (ROOT / "B06_Section" / "B06_Section_UI_Adjust_Dock.ts").read_text(encoding="utf-8")