Files
Aislo/B05_Profile/B05_Profile_UI_RouteEdit_Rotate.ts
T
eomsangdonandClaude Opus 5 f952ac7ffd fix(git): 병합이 떨군 파일 22개와 되돌아간 파일 35개를 되살림
무슨 일이 있었나
랩탑 줄의 병합 `20ba886c`(Merge origin/main_desktop_1·main_laptop_1·sub_desktop_1 into
sub_laptop_1)가 우리 파일 22개를 떨구고 35개 파일의 내용을 옛것으로 되돌림. 손으로 지운
커밋은 없고 **병합 자체가 떨군 것**임. 그것이 `origin/dev`·`main_laptop_1`·`sub_laptop_1`·
`CODEX` 까지 퍼졌고(데스크탑 둘만 무사), 이 창의 병합 `d92c1f2b` 로 들어옴.

잃었던 것
- 공용 — `common_util_provenance.py` · `ui_template_provenance.ts`
- B08 — 근거 사전 · 좌측 패널 상자 모듈 · 토량환산계수 칸
- B09 — 근거 사전 셋
- B05 — 계획노선 편집 모듈 아홉 · 지형 라우터 · B04 지도 모듈
- 시험 셋과, 35개 파일 안의 최근 작업(환산계수 고르기 · 근거 호버 배선 등)

어떻게 되살렸나
`611a2b40`(병합 직전, 전부 온전)에서 `git show <커밋>:<경로>` 로 내용만 꺼내 되돌림.
이력은 안 건드림. ⚠ HEAD 에만 있던 「추가 816줄」은 랩탑의 새 작업이 아니라 **되살아난
옛 코드**였음(B05 편집은 모듈로 쪼개기 전 덩어리 · B08 라우터는 환산계수 고르기 전 옛
상수판). 되돌릴 시점 이후의 **진짜 새 커밋은 둘뿐**이라 그 둘만 패치로 다시 얹음 —
`9f827bf6`(리로드 빌드 고리 끊기, 데스크탑 보조) · `b9bca6b3`(B06 조정창 1px, 랩탑).
위키 여덟은 코덱스 몫이라 손대지 않음.

자체검증 — 양쪽 작업이 다 살아 있음을 짚어 확인: `main.py` 의 「개발 서버는 살려 둔다」 ·
`B05_Profile_Engine_Grade.py` 의 `plan_curve_length_limit_m` · `B08_..._EarthworkGrid.ts` 의
`attachProvenance`. `tsc --noEmit` 통과 · `pytest -q` **1317 passed, 28 skipped**
(되살리기 전에는 시험 둘이 수집 단계에서 깨져 있었음).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RANEBHns1S4tkmsYwewtk
2026-09-12 18:18:57 +09:00

84 lines
3.7 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_RouteEdit_Rotate.ts
* 지도 회전 — 반시계·시계 단추 하나씩, 누를 때마다 **그림 전체**가 한 칸 돈다
* (계획서 0-9 ⑯, 2026-09-12 사용자 지시).
*
* 축은 **캔버스 한가운데**다. 그리기는 캔버스 변환으로 한 번에 돌리고(글자도 함께 돈다 —
* CAD 도면과 같은 방식), 집기는 들어온 화면 자리를 **거꾸로 돌려** 안 돌린 좌표로 바꿔 쓴다.
* 그래야 눈에 보이는 자리와 잡히는 자리가 같다.
* ========================================================================== */
/** 단추 한 번에 도는 각(도) — 15°씩 스물넷이면 한 바퀴. 90°씩이면 맞출 자리가 넷뿐이고,
* 5°씩이면 한 바퀴에 일흔두 번이라 성가시다. */
const ROTATE_STEP_DEG = 15;
/** **글자를 눈높이로 세울지**(계획서 0-9 ㉚, 2026-09-12 사용자 지시).
*
* 그림만 돌고 숫자는 늘 바로 서게 한다 — 180° 로 돌리면 글자가 뒤집혀 안 읽히기 때문이다.
* 비용은 라벨 하나에 변환 한 번뿐이라 그림을 다시 그리는 값에 묻힌다.
*
* ⚠ **되돌리려면 이 값을 `false` 로만 바꾸면 된다** — 그러면 글자도 그림과 함께 돈다
* (CAD 도면과 같은 방식). 사용자가 화면을 보고 판단할 수 있게 한 자리에 모아 두었다. */
export const UPRIGHT_LABELS = true;
export interface MapRotationParams {
/** 단추가 들어 있는 모달 — `[data-act="rotate-ccw"]`·`rotate-cw` 를 찾는다. */
overlay: HTMLElement;
/** 지금 캔버스 크기 — 회전축(한가운데)을 잡는 데 쓴다. */
size: () => { width: number; height: number };
/** 각이 바뀌었다 — 호출부가 다시 그린다. */
onChange: () => void;
}
export interface MapRotation {
/** 지금 돌린 각(라디안). 그리기가 캔버스 변환에 그대로 쓴다. */
radians: () => number;
/** 화면에 보이는 자리 → 그리기 좌표(돌리기 전). */
unrotate: (px: number, py: number) => [number, number];
/** 그리기 좌표 → 화면에 보이는 자리. 떠 있는 패널을 노드 옆에 붙일 때 쓴다. */
rerotate: (px: number, py: number) => [number, number];
/** 화면에서 민 만큼(dx, dy) → 그림 좌표의 만큼. 팬·휠 확대 보정용. */
unrotateDelta: (dx: number, dy: number) => [number, number];
/** 글자를 세울 각(라디안) — 그리기가 라벨마다 이만큼 되돌린다. 안 세우면 0. */
uprightRad: () => number;
}
export function createMapRotation(params: MapRotationParams): MapRotation {
let radians = 0;
const spin = (px: number, py: number, angle: number): [number, number] => {
if (!angle) return [px, py];
const { width, height } = params.size();
const cx = width / 2;
const cy = height / 2;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const dx = px - cx;
const dy = py - cy;
return [cx + dx * cos - dy * sin, cy + dx * sin + dy * cos];
};
for (const [act, sign] of [
["rotate-ccw", -1],
["rotate-cw", 1],
] as const) {
params.overlay.querySelector(`[data-act="${act}"]`)?.addEventListener("click", () => {
radians += (sign * ROTATE_STEP_DEG * Math.PI) / 180;
params.onChange();
});
}
return {
radians: () => radians,
unrotate: (px, py) => spin(px, py, -radians),
rerotate: (px, py) => spin(px, py, radians),
uprightRad: () => (UPRIGHT_LABELS ? -radians : 0),
unrotateDelta: (dx, dy) => {
if (!radians) return [dx, dy];
const cos = Math.cos(-radians);
const sin = Math.sin(-radians);
return [dx * cos - dy * sin, dx * sin + dy * cos];
},
};
}