Files
Aislo/E04_Stab3D/E04_Stab3D_UI_Settings.ts
T
eomsangdonandClaude Opus 5.5 15ce7cdd0b feat(E04): 3D 해석 = 실제 엔진(533-1 합성 지형 · 533-2 구 · 타원체 탐색) 잇기 · 탐색 진행률 · 끊기 고리 · 건기/우기 · 기준안전율 판정 · 방법별 F · 못 뚫는 층 잘린 활동면 + 엔진 잇기 시험 (533-3)
- _Search 에 on_progress · should_stop(기본 None · 결과 불변 시험) — 533-2 시험 14 그대로 통과
- 탐색 노드 = 중심선 ± 탐색 폭 · 구간 누가 안 · 격자 = 그 밖 깊이 + 10 m 여유
- 미리보기(가짜) 엔진 지움 · 방법 고르기 → 조건(건기 · 우기) · Hovland 는 같은 임계면 비교 값

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:01:30 +09:00

142 lines
4.3 KiB
TypeScript

/* =============================================================================
* E04_Stab3D_UI_Settings.ts
* E04 좌측 탐색 설정 칸(PLAN 533-3) — 구간 시작 · 끝 · 기본(조건 · 모양 · 탐색 폭) · 고급(접힘 ·
* 기둥 · 구 중심 격자 · 반지름 증분 · 깊이 · 부피 제약) · [기본값]. 기본값 = 서버 SearchSettings.
* ========================================================================== */
import { createPanelCard } from "@ui/ui_template_collapsible";
import {
createButton,
createInputField,
createSelectField,
el,
type InputFieldHandle,
} from "@ui/ui_template_elements";
import { t, type LocaleKey } from "@ui/ui_template_locale";
export interface Settings {
start_m: number;
end_m: number;
condition: string;
shape: string;
ellipsoid_ratio: number;
box_half_width_m: number;
cell_m: number;
spacing_m: number;
h_min_m: number;
h_max_m: number;
h_step_m: number;
dr_m: number;
max_depth_m: number;
v_min_m3: number;
v_max_m3: number;
}
type NumberKey = Exclude<keyof Settings, "condition" | "shape">;
const BASIC: [NumberKey, LocaleKey][] = [
["ellipsoid_ratio", "E04_Ratio"],
["box_half_width_m", "E04_BoxWidth"],
];
const ADVANCED: [NumberKey, LocaleKey][] = [
["cell_m", "E04_Cell"],
["spacing_m", "E04_Spacing"],
["h_min_m", "E04_HMin"],
["h_max_m", "E04_HMax"],
["h_step_m", "E04_HStep"],
["dr_m", "E04_Dr"],
["max_depth_m", "E04_MaxDepth"],
["v_min_m3", "E04_VMin"],
["v_max_m3", "E04_VMax"],
];
export function buildSettingsCards() {
const fields = new Map<NumberKey, InputFieldHandle>();
const listeners: (() => void)[] = [];
const changed = (): void => listeners.forEach((fn) => fn());
const number = (key: NumberKey, label: LocaleKey): HTMLElement => {
const field = createInputField({ label: t(label), type: "number", compact: true });
field.input.step = "any";
field.input.dataset.key = key;
field.input.addEventListener("change", changed);
fields.set(key, field);
return field.root;
};
const rangeRow = el("div", { className: "e04-range" });
rangeRow.append(number("start_m", "E04_Start"), number("end_m", "E04_End"));
const shape = createSelectField({
label: t("E04_Shape"),
compact: true,
options: (["both", "sphere"] as const).map((v) => ({
value: v,
text: t(`E04_Shape_${v}` as LocaleKey),
})),
onChange: changed,
});
const condition = createSelectField({
label: t("E04_Condition"),
compact: true,
options: (["dry", "wet"] as const).map((v) => ({
value: v,
text: t(`E04_Condition_${v}` as LocaleKey),
})),
onChange: changed,
});
const basicCard = createPanelCard(t("E04_Card_Search"), false);
basicCard.append(condition.root, shape.root, ...BASIC.map(([k, l]) => number(k, l)));
let defaults: Partial<Settings> = {};
const advancedCard = createPanelCard(t("E04_Card_Advanced"), true);
const grid = el("div", { className: "e04-advanced" });
grid.append(...ADVANCED.map(([k, l]) => number(k, l)));
advancedCard.append(
grid,
createButton({
label: t("E04_Defaults"),
variant: "ghost",
size: "sm",
onClick: () => {
const range = { start_m: read().start_m, end_m: read().end_m };
write({ ...read(), ...defaults, ...range, condition: "dry" });
changed();
},
}),
);
function read(): Settings {
const out = { shape: shape.select.value, condition: condition.select.value } as Settings;
for (const [key, field] of fields) out[key] = Number(field.input.value);
return out;
}
function write(values: Partial<Settings>): void {
for (const [key, field] of fields) {
const value = values[key];
if (typeof value === "number" && Number.isFinite(value)) field.input.value = String(value);
}
if (values.shape) shape.select.value = values.shape;
if (values.condition) condition.select.value = values.condition;
}
return {
rangeRow,
basicCard,
advancedCard,
read,
write,
setRange(start: number, end: number) {
write({ start_m: start, end_m: end });
changed();
},
setDefaults(next: Partial<Settings>, maxSection: number) {
defaults = next;
fields.get("end_m")!.input.title = `≤ ${maxSection} m`;
},
onChange(fn: () => void) {
listeners.push(fn);
},
};
}