feat(B05): 367-3 자유지정 되돌리기 · 다시하기 — 자유지정 안 한 걸음씩 · 큰 이력과 따로 · 적용 막히면 열린 채

- design_profile_free past/future(상태 바뀐 클릭만 · 새 클릭이 future 비움) · undo/redo/canUndo/canRedo · reset 비움 · Backspace = undo
- 자유지정 동안 ↶ ↷ 단추 · Ctrl+Z · Ctrl+Shift+Z = 자유지정 쪽만(되돌릴 것 없어도 큰 이력 안 감)
- applyEdits 성공 여부 돌려줌 · 막히면(기울기 · 흙덮기) 자유지정 열린 채
- 시험 helper_215_1 ⑥ ⑦ · test_215_1 · 345_3 창 넓힘

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
This commit is contained in:
2026-10-17 11:53:36 +09:00
co-authored by Claude Opus 5.5
parent ddc6fe2311
commit 84876b747b
6 changed files with 171 additions and 27 deletions
+72 -3
View File
@@ -26,7 +26,9 @@ function loadTs(file) {
}
const free = loadTs(path.join(ROOT, "A00_Common", "design", "logic", "design_profile_free.ts"));
const align = loadTs(path.join(ROOT, "B05_Profile", "logic", "B05_Profile_UI_Profile_Alignment.ts"));
const align = loadTs(
path.join(ROOT, "B05_Profile", "logic", "B05_Profile_UI_Profile_Alignment.ts"),
);
// 전체 측점 폴리라인(20m 간격 · 0~200m) — 계획고 = 지반고, 마디마다 기울기가 다르다.
const stations = Array.from({ length: 11 }, (_, i) => i * 20);
@@ -124,14 +126,23 @@ out.snapOutside = free.snapStation(stations, 190, [40, 160]);
out.radiiKeys = Object.keys(next.curve_radii).sort();
const built = align.buildAlignment(base, next);
const pvi = (s) => built.pvi.find((n) => Math.abs(n.chainage_m - s) < 1e-6).elevation_m;
out.pvi = { 40: pvi(40), 60: pvi(60), 80: pvi(80), 100: pvi(100), 120: pvi(120), 140: pvi(140), 160: pvi(160) };
out.pvi = {
40: pvi(40),
60: pvi(60),
80: pvi(80),
100: pvi(100),
120: pvi(120),
140: pvi(140),
160: pvi(160),
};
out.ground = { 40: z0(40), 160: z0(160) };
const g1 = (103.46 - z0(40)) / 40;
const g2 = (101.2 - 103.46) / 40;
out.expectR80 = Number((20 / Math.abs(g2 - g1)).toFixed(6));
out.r80 = next.curve_radii["80.000"];
out.curve80L = built.curves.find((c) => Math.abs(c.chainage_m - 80) < 1e-6)?.l_m ?? null;
out.outsideUntouched = !("20.000" in next.station_offsets) && !("180.000" in next.station_offsets);
out.outsideUntouched =
!("20.000" in next.station_offsets) && !("180.000" in next.station_offsets);
}
// ④ Enter = 끝(마지막 점 → 끝 세로선) · 끝 세로선에서 거꾸로 시작.
@@ -172,4 +183,62 @@ out.snapOutside = free.snapStation(stations, 190, [40, 160]);
out.enterOutsideDrawing = draw.key("Enter");
}
// ⑥ 되돌리기 · 다시하기(367-3) — 점 둘 → ↶ → ↷ → 새 클릭이 ↷ 지움 · apply 안 불림 · 범위까지 거꾸로.
{
const { draw, log } = harness();
out.undoEmpty = { can: draw.canUndo(), backspace: draw.key("Backspace") };
draw.click(40, 0, stations);
draw.click(40, 0, stations); // 같은 세로선 다시 — 상태 그대로 · 안 쌓임
draw.click(160, 0, stations);
draw.click(40, 0, stations);
draw.click(80, 102, stations);
draw.click(120, 103, stations);
const dots = () => draw.marks().dots.map((d) => d.chainage_m);
draw.undo();
out.undo1 = { dots: dots(), canRedo: draw.canRedo() };
draw.redo();
out.redo1 = { dots: dots(), canRedo: draw.canRedo() };
draw.undo();
draw.click(100, 104, stations); // 새 클릭 = future 비움
out.newClick = { dots: dots(), canRedo: draw.canRedo() };
const phases = [];
while (draw.canUndo()) {
draw.undo();
phases.push(`${draw.phase()}:${draw.marks().edges.length}`);
}
out.undoAll = phases;
out.undoApplied = log.applied.length;
out.undoClosed = log.closed;
draw.redo();
draw.redo();
out.redoTwice = { phase: draw.phase(), band: draw.marks().band };
draw.reset();
out.resetClears = { undo: draw.canUndo(), redo: draw.canRedo() };
}
// ⑦ 적용이 막히면(apply = false) 자유지정 열린 채 · 점 그대로.
{
let tries = 0;
const draw = free.createFreeDraw({
base: () => base,
alignment: () => align.buildAlignment(base, align.emptyEdits()),
edits: () => align.emptyEdits(),
apply: () => ((tries += 1), false),
close: () => (tries += 100),
changed: () => undefined,
});
draw.click(40, 0, stations);
draw.click(160, 0, stations);
draw.click(40, 0, stations);
draw.click(80, 102, stations);
draw.key("Enter");
draw.click(160, 0, stations);
out.blocked = {
tries,
phase: draw.phase(),
dots: draw.marks().dots.length,
canUndo: draw.canUndo(),
};
}
process.stdout.write(JSON.stringify(out));
@@ -115,6 +115,38 @@ def test_Backspace_와_Esc_취소(out):
assert out["enterOutsideDrawing"] is False
def test_자유지정_되돌리기_다시하기(out):
# 367-3 ⑥ 점 둘 → ↶ → ↷ → 새 클릭이 ↷ 지움 · 큰 이력(apply · close) 안 건드림
assert out["undoEmpty"] == {"can": False, "backspace": False}
assert out["undo1"] == {"dots": [40, 80], "canRedo": True}
assert out["redo1"] == {"dots": [40, 80, 120], "canRedo": False}
assert out["newClick"] == {"dots": [40, 80, 100], "canRedo": False}
# 한 걸음 = 점 · 시작 · 범위 끝 · 범위 첫 · 같은 세로선 다시 누름은 안 쌓임
assert out["undoAll"] == ["drawing:2", "drawing:2", "armed:2", "range:1", "range:0"]
assert out["undoApplied"] == 0 and out["undoClosed"] == 0
assert out["redoTwice"] == {"phase": "armed", "band": [40, 160]}
assert out["resetClears"] == {"undo": False, "redo": False}
def test_적용_막히면_열린_채(out):
# 367-3 ⑦ Enter · 끝 세로선 둘 다 apply 시도(2) · close 없음 · 점 그대로
assert out["blocked"] == {"tries": 2, "phase": "drawing", "dots": 2, "canUndo": True}
def test_되돌리기_배선_자유지정_먼저():
panel = _read("B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts")
assert 'tools.mode() === "free" ? (free.undo(), freeView.redraw()) : inner.undo()' in panel
assert 'tools.mode() === "free" ? free.canUndo() : inner.canUndo()' in panel
keys = panel[panel.index('isCtrlKey(event, "z") || isCtrlKey(event, "y")') :]
free_at = keys.index('if (tools.mode() === "free") {')
assert (
free_at < keys.index("history.undo()")
and "return;" in keys[free_at : keys.index("flushHold();")]
)
profile = _read("A00_Common/design/design_profile.ts")
assert "function applyEdits(next: AlignmentEdits): boolean {" in profile
def test_도구줄_직선화_오른쪽_자유지정():
tools = _read("B05_Profile/B05_Profile_UI_Profile_Tools.ts")
assert '"자유지정"' in tools
+3 -1
View File
@@ -48,7 +48,9 @@ def test_Ctrl_Z_는_사진첩_한_줄(out):
def test_종단도_Ctrl_Z_줄이_사진첩_하나():
tools = _read("B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts")
at = tools.index('if (isCtrlKey(event, "z") || isCtrlKey(event, "y")) {')
assert "undoOwner" not in tools and "history.undo()" in tools[at : at + 240]
assert (
"undoOwner" not in tools and "history.undo()" in tools[at : at + 600]
) # 367-3 자유지정 분기 앞에 섬
history = _read("B05_Profile/B05_Profile_UI_RouteEdit_History.ts")
assert '"keydown"' not in history and "onPageHistory" in history