- 노면 끝 · 비탈 끝 · 단면 꺾임 · 비탈 마구리 · 구조물 윗면 외곽 · 관 입구 선을 지형에 끼워 걸친 삼각형만 다시 나눔(변 교점 꼭짓점 번호 순 식 · 일직선 마디 되살림) · 색은 면 단위 - 구조물 구멍은 윗면 외곽만 · 풋프린트 안 · 둘레 0.75 m 띠는 솔리드 윗면 아래로 · 관 입구만 구멍 - 손대는 자리 = 행별 비탈 끝 + 5 m 띠(밖 지형 그대로) - 3D 보기 줄 「3D 방식: 지금 · 옛 A · 새 ① · 제안 ②」 — 방식 등록부(B05_Profile_UI_Corridor_Modes.ts) · 설정 키 corridor-mode 하나(옛 두 키 읽어서 옮김) · 기본 새 ① · 제안 ② 는 빈자리(단추 꺼짐) - 226 실측: T자 균열(1 cm 넘음) 298 → 0 · 구조물 둘레 0.1 m 넘음 57 % → 12 % · 색 번짐 p90 3.1 → 0 m · 발끝 어긋남 p90 2.0 → 0.15 m Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rJwJMVL4EE31y77TB11AP
144 lines
6.2 KiB
TypeScript
144 lines
6.2 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_Panel_View.ts
|
|
* 3D 화면 위에 얹는 **보기 조작 줄**(ISO·TOP·… · 표시 켬끔 · 뷰 초기화 · [3D 업데이트]).
|
|
* 좌측 패널 칸이 아니라 뷰어에 붙는다 — `B05_Profile_UI_Panel.ts` 에서 떼어냈다(계획서 24-2b).
|
|
* ========================================================================== */
|
|
|
|
import { L, button, toggleButton } from "./B05_Profile_UI_Panel_Fields";
|
|
import {
|
|
corridorModeId,
|
|
MODE_SLOTS,
|
|
modeOf,
|
|
onCorridorMode,
|
|
setCorridorMode,
|
|
} from "./B05_Profile_UI_Corridor_Modes";
|
|
|
|
/** 뷰 조작 줄이 부르는 것 — 좌측 패널 콜백의 일부다. */
|
|
export interface ViewControlCallbacks {
|
|
onSurfaceVisible: (visible: boolean) => void;
|
|
onCorridorVisible: (visible: boolean) => void;
|
|
onContoursVisible: (visible: boolean) => void;
|
|
onAxesVisible: (visible: boolean) => void;
|
|
onStationLinesVisible: (visible: boolean) => void;
|
|
onStationLabelsVisible: (visible: boolean) => void;
|
|
onSurfaceGrayscale: (grayscale: boolean) => void;
|
|
onView: (view: "iso" | "top" | "front" | "side") => void;
|
|
onProjection: (kind: "perspective" | "ortho") => void;
|
|
onResetView: () => void;
|
|
onCorridorRefresh: () => void | Promise<void>;
|
|
}
|
|
|
|
export function createViewControls(callbacks: ViewControlCallbacks): {
|
|
viewControls: HTMLElement;
|
|
/** [3D 업데이트] 대기 표시 — 편집이 3D에 아직 안 들어갔음을 버튼에 알린다. */
|
|
setCorridorPending: (pending: boolean) => void;
|
|
} {
|
|
const viewControls = document.createElement("div");
|
|
viewControls.className = "b05-route__view-controls";
|
|
const viewButtons = document.createElement("div");
|
|
viewButtons.className = "b05-route__view-group";
|
|
(["iso", "top", "front", "side"] as const).forEach((preset) =>
|
|
viewButtons.append(button(preset.toUpperCase(), () => callbacks.onView(preset), "glass")),
|
|
);
|
|
// 원근·직교 두 단추 — 지금 켜진 쪽을 강조한다. 기본은 직교(카메라 기본과 같음).
|
|
const projectionButtons = (
|
|
[
|
|
["perspective", "원근"],
|
|
["ortho", "직교"],
|
|
] as const
|
|
).map(([kind, label]) => {
|
|
const element = button(
|
|
label,
|
|
() => {
|
|
projectionButtons.forEach(({ kind: other, element: el }) => {
|
|
el.classList.toggle("is-active", other === kind);
|
|
el.setAttribute("aria-pressed", String(other === kind));
|
|
});
|
|
callbacks.onProjection(kind);
|
|
},
|
|
"glass",
|
|
);
|
|
element.classList.toggle("is-active", kind === "ortho");
|
|
element.setAttribute("aria-pressed", String(kind === "ortho"));
|
|
return { kind, element };
|
|
});
|
|
viewButtons.append(...projectionButtons.map(({ element }) => element));
|
|
// 3D 방식 고르기 한 줄(115-5) — 원근 · 직교와 같은 꼴. 등록 안 된 자리(제안 ②)는 꺼짐.
|
|
const modeButtons = document.createElement("div");
|
|
modeButtons.className = "b05-route__view-group";
|
|
const modeLabel = document.createElement("span");
|
|
modeLabel.className = "b05-route__view-label";
|
|
modeLabel.textContent = "3D 방식";
|
|
const modeChoices = MODE_SLOTS.map(([id, label]) => {
|
|
const element = button(label, () => setCorridorMode(id), "glass");
|
|
element.disabled = !modeOf(id);
|
|
if (element.disabled) element.title = "아직 없는 방식입니다.";
|
|
return { id, element };
|
|
});
|
|
const showMode = (): void => {
|
|
const current = corridorModeId();
|
|
modeChoices.forEach(({ id, element }) => {
|
|
element.classList.toggle("is-active", id === current);
|
|
element.setAttribute("aria-pressed", String(id === current));
|
|
});
|
|
};
|
|
showMode();
|
|
onCorridorMode(showMode);
|
|
modeButtons.append(modeLabel, ...modeChoices.map(({ element }) => element));
|
|
const visibilityButtons = document.createElement("div");
|
|
visibilityButtons.className = "b05-route__view-group";
|
|
visibilityButtons.append(
|
|
toggleButton("지표면", true, callbacks.onSurfaceVisible),
|
|
// 예상형상(2026-08-23): ON=공사 후 형상(기본), OFF=원지반 완전체.
|
|
toggleButton("예상형상", true, callbacks.onCorridorVisible),
|
|
toggleButton("등고선", true, callbacks.onContoursVisible),
|
|
toggleButton("축 표시", false, callbacks.onAxesVisible),
|
|
toggleButton(L("B05_Route_Field_StationLines"), true, callbacks.onStationLinesVisible),
|
|
toggleButton(L("B05_Route_Field_StationLabels"), true, callbacks.onStationLabelsVisible),
|
|
// 무지개 고도색이 헷갈릴 때 명도만 남기는 흑백 표시(2026-08-05 사용자 요청).
|
|
// 기본 켜짐(2026-08-23 사용자 확정) — 예상형상 색이 지형 고도색에 묻히지 않는다.
|
|
toggleButton("흑백 지형", true, callbacks.onSurfaceGrayscale),
|
|
);
|
|
// [3D 업데이트](2026-09-01 사용자 지시) — 계획선을 고칠 때마다 3D가 따라오면 조작이
|
|
// 무거워, 갱신을 이 버튼으로 모았다. 편집이 쌓이면 is-pending으로 알리고, 누르는 동안은
|
|
// 이 버튼만 잠근다(화면 전체 잠금 금지 — CLAUDE.md 5장).
|
|
const corridorRefresh = button(
|
|
"3D 업데이트",
|
|
() => {
|
|
corridorRefresh.disabled = true;
|
|
corridorRefresh.textContent = "갱신 중…";
|
|
void Promise.resolve(callbacks.onCorridorRefresh()).finally(() => {
|
|
corridorRefresh.disabled = false;
|
|
corridorRefresh.textContent = "3D 업데이트";
|
|
corridorRefresh.classList.remove("is-pending");
|
|
});
|
|
},
|
|
"glass",
|
|
);
|
|
corridorRefresh.title =
|
|
"계획선 편집을 3D 예상형상·측점선에 반영합니다.\n편집이 쌓이면 버튼에 표시가 붙습니다.";
|
|
const separator1 = document.createElement("span");
|
|
separator1.className = "b05-route__view-separator";
|
|
const separator2 = separator1.cloneNode() as HTMLSpanElement;
|
|
const separator3 = separator1.cloneNode() as HTMLSpanElement;
|
|
const separator4 = separator1.cloneNode() as HTMLSpanElement;
|
|
viewControls.append(
|
|
viewButtons,
|
|
separator1,
|
|
modeButtons,
|
|
separator4,
|
|
visibilityButtons,
|
|
separator2,
|
|
button("뷰 초기화", callbacks.onResetView, "glass"),
|
|
separator3,
|
|
corridorRefresh,
|
|
);
|
|
|
|
return {
|
|
viewControls,
|
|
setCorridorPending(pending: boolean) {
|
|
corridorRefresh.classList.toggle("is-pending", pending);
|
|
},
|
|
};
|
|
}
|