B05 세월교 옵션에 BOX암거 날개벽 한 벌(설치·짧은쪽 높이 1m·길이 2m·각도 45°)을 그대로 붙이고, B06 횡단도에 세월교 구체를 그린다. 구체는 양측 ㄴ형 집수정 측벽 + 그 사이를 잇는 바닥판 + 바닥판 상면에 안힌 관 + 관 위 성토(노체) 채움이다. - 바닥판 편측 연장 = 날개벽 길이 × cos(각도) — 각도는 관축 기준 벌어짐각 - 측벽은 buildBasin 재사용이라 1:0.3 기움·좌우·상하 대각 이동·계류측 성토부선이 집수정과 같은 규칙으로 따라온다 (기슭막이 다단·재질과는 연계하지 않음) - 구체는 월류 폭만큼 도로 방향으로 이어져 기준 측점 전후 절반까지 붙는다 - 조정창: 측벽 높이·좌우·상하, 관경·수량(정본 pipe_points 되쓰기). 높이·좌우·상하는 세션 + design.ford_adjust에 저장 - 3D 예상형상: 구체 부재는 월류 폭 구간 스윕, 관은 련수만큼 폭 안 등간격 - 부재 두께: 바닥판 0.3m(교본 물넘이 물받이 최소 30cm), 측벽 0.2m(집수정 승계) 700줄 제한으로 카드 렌더러·측점 제어기·페이지에서 배선을 분리했다 (_Cross_View_Ford, _Page_Ford_Controls, _Page_Patches, Culvert_Const 구간값 헬퍼). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
213 lines
8.3 KiB
TypeScript
213 lines
8.3 KiB
TypeScript
/* =============================================================================
|
||
* B06_Section_UI_Cross_Ford_Panel.ts
|
||
* 세월교 측벽 **조정 오버레이 창**(2026-08-25 사용자 확정).
|
||
*
|
||
* 배수관 조정창(`_Cross_Structure_Panel.ts`)은 기슭막이 4축·다단·구간값·연동까지
|
||
* 안고 있어 592줄이다 — 세월교는 조작 축이 다르고 그쪽에 얹으면 700줄을 넘긴다.
|
||
* 그래서 창을 따로 두되 **CSS 클래스와 조작 관례(십자 D-pad·0.1m 눈금)는 같다**.
|
||
*
|
||
* 조작(사용자 확정): 측벽 높이 · 측벽 좌우 · 측벽 상하(1:1.2 대각) · 관경 · 관 수량.
|
||
* 바닥판 연장은 날개벽 각도가 정하므로 여기서 직접 만지지 않는다(B05 옵션).
|
||
* ========================================================================== */
|
||
|
||
import type { FordAdjust, FordWallAdjust, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom";
|
||
|
||
/** 조정창이 쓰는 값·동작. 카드가 자기 상태에 맞춰 물려 준다. */
|
||
export interface FordPanelDeps {
|
||
/** 지금 조작값(적용된 결과 — 한계에 잘린 뒤 값). */
|
||
adjustFor: (role: FordWallRole) => FordWallAdjust;
|
||
/** 실제 그려진 벽 높이(m) — 자동일 때도 숫자를 보여 준다. */
|
||
heightFor: (role: FordWallRole) => number;
|
||
/** 높이 ±0.1m. */
|
||
nudgeHeight: (role: FordWallRole, deltaM: number) => void;
|
||
/** 좌우: 화면 좌(+)/우(−)로 미는 양(m). 부호 환산은 카드가 한다. */
|
||
nudge: (role: FordWallRole, screenDeltaM: number) => void;
|
||
/** 상하: 1:1.2 사면을 타는 대각 이동(수평 성분 m, + = 사면 아래). */
|
||
nudgeSlope: (role: FordWallRole, deltaM: number) => void;
|
||
/** 자동 자리로 되돌린다(세 축 모두). */
|
||
reset: (role: FordWallRole) => void;
|
||
/** 관경(mm)·수량(련) — 값은 B05 정본(`pipe_points`)에 되돌려 쓴다. */
|
||
pipeDiameterMm: () => number;
|
||
setPipeDiameterMm: (value: number) => void;
|
||
pipeCount: () => number;
|
||
setPipeCount: (value: number) => void;
|
||
/** 바닥판 길이(m) — 날개벽 투영이 정한 값을 읽기 전용으로 보여 준다. */
|
||
slabLengthM: () => number;
|
||
/** 창을 닫는다 = 구조물 선택 해제. */
|
||
close: () => void;
|
||
}
|
||
|
||
export interface FordPanelHandle {
|
||
root: HTMLElement;
|
||
/** null이면 숨긴다. 값이 오면 그 측벽 기준으로 다시 그린다. */
|
||
show: (role: FordWallRole | null) => void;
|
||
}
|
||
|
||
/** 한 걸음 — 집수정 9키와 같은 0.1m 눈금. */
|
||
const STEP_M = 0.1;
|
||
/** 관경 선택지(mm) — B05 레지스트리 `ford_bridge.pipe_diameter_mm` choices와 같다. */
|
||
const DIAMETER_CHOICES_MM = [800, 1000, 1200, 1500];
|
||
|
||
function makeButton(label: string, title: string, onClick: () => void): HTMLButtonElement {
|
||
const button = document.createElement("button");
|
||
button.type = "button";
|
||
button.className = "b06-structure-panel__btn";
|
||
button.textContent = label;
|
||
button.title = title;
|
||
button.addEventListener("click", (event) => {
|
||
// 카드 선택·팬으로 번지면 도면이 다시 그려져 맞춰 둔 배율이 날아간다.
|
||
event.stopPropagation();
|
||
onClick();
|
||
});
|
||
return button;
|
||
}
|
||
|
||
/** 항목 행 — 1행 이름 라벨 + 2행 값 조작(배수관 조정창과 같은 2행 구조). */
|
||
function makeRow(labelText: string): { row: HTMLElement; controls: HTMLElement } {
|
||
const row = document.createElement("div");
|
||
row.className = "b06-structure-panel__struct";
|
||
const label = document.createElement("span");
|
||
label.className = "b06-structure-panel__label";
|
||
label.textContent = labelText;
|
||
const controls = document.createElement("div");
|
||
controls.className = "b06-structure-panel__controls";
|
||
row.append(label, controls);
|
||
return { row, controls };
|
||
}
|
||
|
||
/** 세월교 측벽 조정 오버레이 창을 만든다. 처음에는 숨어 있다. */
|
||
export function buildFordPanel(deps: FordPanelDeps): FordPanelHandle {
|
||
const root = document.createElement("div");
|
||
root.className = "b06-structure-panel is-hidden";
|
||
root.addEventListener("click", (event) => event.stopPropagation());
|
||
|
||
const title = document.createElement("span");
|
||
title.className = "b06-structure-panel__title";
|
||
|
||
const value = document.createElement("div");
|
||
value.className = "b06-structure-panel__value";
|
||
const heightLine = document.createElement("span");
|
||
const moveLine = document.createElement("span");
|
||
const slabLine = document.createElement("span");
|
||
value.append(heightLine, moveLine, slabLine);
|
||
|
||
let current: FordWallRole | null = null;
|
||
const act = (run: (role: FordWallRole) => void) => () => {
|
||
if (current) run(current);
|
||
};
|
||
|
||
const heightRow = makeRow("측벽 높이");
|
||
heightRow.controls.append(
|
||
makeButton(
|
||
"−",
|
||
"측벽 높이 0.1m 낮추기",
|
||
act((role) => deps.nudgeHeight(role, -STEP_M)),
|
||
),
|
||
makeButton(
|
||
"+",
|
||
"측벽 높이 0.1m 높이기",
|
||
act((role) => deps.nudgeHeight(role, STEP_M)),
|
||
),
|
||
);
|
||
|
||
const moveRow = makeRow("이동");
|
||
moveRow.controls.classList.add("b06-structure-panel__buttons");
|
||
const dpad = (
|
||
label: string,
|
||
slot: "up" | "down" | "left" | "right" | "reset",
|
||
tip: string,
|
||
onClick: () => void,
|
||
): HTMLButtonElement => {
|
||
const button = makeButton(label, tip, onClick);
|
||
button.classList.add(`b06-structure-panel__btn--${slot}`);
|
||
return button;
|
||
};
|
||
moveRow.controls.append(
|
||
dpad(
|
||
"▲",
|
||
"up",
|
||
"사면 위로(1:1.2 대각)",
|
||
act((role) => deps.nudgeSlope(role, -STEP_M)),
|
||
),
|
||
dpad(
|
||
"◀",
|
||
"left",
|
||
"화면 왼쪽으로",
|
||
act((role) => deps.nudge(role, STEP_M)),
|
||
),
|
||
dpad("↺", "reset", "이 측벽 조정값 초기화", act(deps.reset)),
|
||
dpad(
|
||
"▶",
|
||
"right",
|
||
"화면 오른쪽으로",
|
||
act((role) => deps.nudge(role, -STEP_M)),
|
||
),
|
||
dpad(
|
||
"▼",
|
||
"down",
|
||
"사면 아래로(1:1.2 대각)",
|
||
act((role) => deps.nudgeSlope(role, STEP_M)),
|
||
),
|
||
);
|
||
|
||
const pipeRow = makeRow("관경 · 수량");
|
||
const diameter = document.createElement("select");
|
||
diameter.className = "b06-structure-panel__select";
|
||
for (const mm of DIAMETER_CHOICES_MM) {
|
||
const option = document.createElement("option");
|
||
option.value = String(mm);
|
||
option.textContent = `Ø${mm}`;
|
||
diameter.append(option);
|
||
}
|
||
diameter.addEventListener("change", () => deps.setPipeDiameterMm(Number(diameter.value)));
|
||
const countValue = document.createElement("span");
|
||
countValue.className = "b06-structure-panel__count";
|
||
pipeRow.controls.append(
|
||
diameter,
|
||
makeButton("−", "관 수량 1련 줄이기", () =>
|
||
deps.setPipeCount(Math.max(1, deps.pipeCount() - 1)),
|
||
),
|
||
countValue,
|
||
makeButton("+", "관 수량 1련 늘리기", () => deps.setPipeCount(deps.pipeCount() + 1)),
|
||
);
|
||
|
||
const closeButton = makeButton("✕", "닫기", () => deps.close());
|
||
closeButton.classList.add("b06-structure-panel__close");
|
||
|
||
root.append(title, closeButton, value, heightRow.row, moveRow.row, pipeRow.row);
|
||
|
||
function render(): void {
|
||
if (!current) return;
|
||
const role = current === "inlet" ? "유입" : "유출";
|
||
title.textContent = `세월교 ${role} 측벽`;
|
||
const adjust: FordWallAdjust = deps.adjustFor(current);
|
||
heightLine.textContent =
|
||
`높이 ${deps.heightFor(current).toFixed(2)}m` + (adjust.heightM === null ? " (자동)" : "");
|
||
moveLine.textContent = `좌우 ${adjust.lateralM.toFixed(1)}m · 상하 ${adjust.slopeM.toFixed(1)}m`;
|
||
slabLine.textContent = `바닥판 ${deps.slabLengthM().toFixed(2)}m (날개벽 각도 종속)`;
|
||
diameter.value = String(deps.pipeDiameterMm());
|
||
countValue.textContent = `${deps.pipeCount()}련`;
|
||
}
|
||
|
||
return {
|
||
root,
|
||
show(role) {
|
||
current = role;
|
||
root.classList.toggle("is-hidden", role === null);
|
||
render();
|
||
},
|
||
};
|
||
}
|
||
|
||
/** 측점별 세월교 조작값 제어 — 페이지(세션·정본)가 구현한다. */
|
||
export interface FordControl {
|
||
adjustFor: (chainageM: number) => FordAdjust;
|
||
update: (chainageM: number, role: FordWallRole, patch: Partial<FordWallAdjust>) => void;
|
||
reset: (chainageM: number, role: FordWallRole) => void;
|
||
/** 관경(mm)·수량(련) 저장 — B05 정본(`pipe_points`)으로 간다. */
|
||
setPipe: (chainageM: number, patch: { pipe_diameter_mm?: number; pipe_count?: number }) => void;
|
||
/** 지금 선택된 측벽(측점별). */
|
||
selectedFor: (chainageM: number) => FordWallRole | null;
|
||
select: (chainageM: number, role: FordWallRole | null) => void;
|
||
}
|