feat(rebar): 확대기초 배근 입력 8개 (피복 3·여유·격자 s·s_v·e_top·n), FT01·FT02 검사
- core/rebar/footing-settings.ts: 입력 정의, 기본값(확대기초철근 (1).md), 범위(제안), 검사 (FT01 범위, FT02 n 정수) - 확대기초 계산 함수가 설정을 인자로 받음 (기본값 = 기존 값, 기존 호출은 그대로) - 3D 확대기초 그림의 125·100 하드코딩 제거, 철근표·3D·기둥 층 높이가 같은 설정을 씀 - 패널: 상면·측면·하면 피복을 칸 분리, s_v와 e_top 분리. 고정 규칙(k=2, Δ₂=100, 전단 4s·엇갈림 2s, cycle 교대)과 B2·B6 디리는 읽기 전용으로 표시 - 기본값에서 P1 결과 불변 (기존 테스트 통과) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
550b508e7a
commit
5a20a18185
11 files changed
+281
-96
No files matched your search
+77
@@ -30,6 +30,8 @@ import { DEFAULT_BLOCK_REBAR, type BlockRebarSettings } from './core/rebar/beari
|
||||
import { COPING_REBAR_RANGES, copingSettingsIssues, type CopingRebarSettings, DEFAULT_COPING_REBAR, DEFAULT_TOP_SPLICE, type TopSpliceSettings } from './core/rebar/coping-settings';
|
||||
import { COLUMN_REBAR_RANGES, type ColumnRebarSettings, columnSettingsIssues, DEFAULT_COLUMN_REBAR } from './core/rebar/column-settings';
|
||||
import { columnInputFor, columnRebar } from './core/rebar/column';
|
||||
import { DEFAULT_FOOTING_REBAR, FOOTING_REBAR_RANGES, type FootingRebarSettings, footingSettingsIssues } from './core/rebar/footing-settings';
|
||||
import { legB2 } from './core/rebar/footing';
|
||||
import { columnElevationSvg } from './view/column-elevation-svg';
|
||||
import { type ColumnSection, columnSectionSvg } from './view/column-section-svg';
|
||||
import { groundSectionSvg } from './view/ground-svg';
|
||||
@@ -1290,6 +1292,80 @@ function renderColumnSettings(box: HTMLDivElement, pier: Pier): void {
|
||||
box.appendChild(wrap);
|
||||
}
|
||||
|
||||
/** 확대기초 배근 입력 (승인 2026-10-26, 시안 ③ 확대기초). Fixed rules (k = 2, Δ₂ = 100, 전단 4s · 엇갈림 2s, cycle 교대) are shown, not edited. */
|
||||
const FOOTING_REBAR_FIELDS: { key: keyof FootingRebarSettings; label: string }[] = [
|
||||
{ key: 'cTop', label: '상면 피복 (철근 중심, mm)' },
|
||||
{ key: 'cSide', label: '측면 피복 (mm)' },
|
||||
{ key: 'cBottom', label: '하면 피복 (mm)' },
|
||||
{ key: 'eMin', label: '첫 철근 최소 여유 (mm)' },
|
||||
{ key: 's', label: '격자 실간격 s (mm)' },
|
||||
{ key: 'sV', label: 'B10 높이 간격 s_v (mm)' },
|
||||
{ key: 'eTop', label: 'B5 아래 최소 여유 e_top (mm)' },
|
||||
{ key: 'n', label: '조립 단 배수 n (B10 간격의 n배)' },
|
||||
];
|
||||
|
||||
/** 적용: the values go to the draft and the model is rebuilt only when every check passes (FT01·FT02). */
|
||||
function applyFootingRebar(name: string, next: FootingRebarSettings): void {
|
||||
const p = state.piers.find((q) => q.input.name === name);
|
||||
if (!p) return;
|
||||
p.draft = { ...p.draft, footingRebar: next };
|
||||
regenerateOne(name);
|
||||
}
|
||||
|
||||
function renderFootingSettings(box: HTMLDivElement, pier: Pier): void {
|
||||
if (!pier.model) return;
|
||||
const wrap = el('div', undefined, 'bearing-ref');
|
||||
const current: FootingRebarSettings = { ...DEFAULT_FOOTING_REBAR, ...pier.draft.footingRebar };
|
||||
wrap.appendChild(el('p', '확대기초 배근 입력 (기본값 = 확대기초철근 (1).md · 적용을 눌러야 반영)', 'lbl'));
|
||||
const grid = el('div', undefined, 'edit-fields');
|
||||
const inputs = new Map<keyof FootingRebarSettings, HTMLInputElement>();
|
||||
for (const f of FOOTING_REBAR_FIELDS) {
|
||||
const row = el('div', undefined, 'edit-row');
|
||||
const label = el('label', f.label);
|
||||
const input = el('input');
|
||||
input.type = 'number';
|
||||
input.step = 'any';
|
||||
input.value = String(current[f.key]);
|
||||
input.id = `footing-${f.key}`;
|
||||
label.htmlFor = input.id;
|
||||
inputs.set(f.key, input);
|
||||
const [lo, hi] = FOOTING_REBAR_RANGES[f.key];
|
||||
const changed = current[f.key] !== DEFAULT_FOOTING_REBAR[f.key] ? ' · 기본과 다름' : '';
|
||||
row.append(label, input, el('span', `범위 ${lo}–${hi} · 기본 ${DEFAULT_FOOTING_REBAR[f.key]}${changed}`, 'muted small'));
|
||||
grid.appendChild(row);
|
||||
}
|
||||
wrap.appendChild(grid);
|
||||
// Fixed rules and derived values (읽기 전용). Derived from the current settings of the first footing.
|
||||
const fs = current;
|
||||
const fixed = el('div', undefined, 'muted small');
|
||||
const H = pier.model.footings[0] ? (pier.model.footings[0].top - pier.model.footings[0].bottom) * 1000 : undefined;
|
||||
fixed.textContent = [
|
||||
`상면·2단 간격 = 2s (고정) ${2 * fs.s} mm`,
|
||||
`하면 1·2단 간격 Δ₂ (고정) 100 mm`,
|
||||
`전단 B9 간격 4s · 행 엇갈림 2s (고정) ${4 * fs.s} · ${2 * fs.s} mm`,
|
||||
`조립 평면 간격 4s · 연직 n·s_v (자동) ${4 * fs.s} · ${fs.n * fs.sV} mm`,
|
||||
`cycle 배치: 교대 배치 (고정)`,
|
||||
H === undefined ? 'B2·B6 디리 (자동): 확대기초 없음' : `B2·B6 디리 (자동) z_B1 − z_B24 = ${legB2(H, fs)} mm`,
|
||||
].join(' · ');
|
||||
wrap.appendChild(fixed);
|
||||
const msg = el('p', undefined, 'muted small');
|
||||
const apply = el('button', '적용', 'btn small');
|
||||
apply.type = 'button';
|
||||
apply.addEventListener('click', () => {
|
||||
const next = { ...current };
|
||||
for (const f of FOOTING_REBAR_FIELDS) next[f.key] = Number(inputs.get(f.key)?.value);
|
||||
const issues = footingSettingsIssues(next);
|
||||
if (issues.length > 0) {
|
||||
msg.textContent = issues.map((i) => i.message).join(' · ');
|
||||
setError('확대기초 배근 입력을 확인하세요. 값은 반영되지 않았습니다.');
|
||||
return;
|
||||
}
|
||||
applyFootingRebar(pier.input.name, next);
|
||||
});
|
||||
wrap.append(msg, apply);
|
||||
box.appendChild(wrap);
|
||||
}
|
||||
|
||||
function renderCopingDrawings(box: HTMLDivElement, pier: Pier): void {
|
||||
if (!pier.model || !rebar3d) return;
|
||||
const L = pier.model.copingL;
|
||||
@@ -1366,6 +1442,7 @@ function renderRebar(): void {
|
||||
if (state.rebarMember === 'coping' && rebar3d) renderCopingDrawings(box, pier);
|
||||
if (state.rebarMember === 'coping') renderCopingSettings(box, pier);
|
||||
if (state.rebarMember === 'column') renderColumnSettings(box, pier);
|
||||
if (state.rebarMember === 'footing') renderFootingSettings(box, pier);
|
||||
const shown = rows.filter((r) => r.member === state.rebarMember);
|
||||
box.appendChild(rebarSelectBar(shown.map((r) => r.name)));
|
||||
const table = el('table', undefined, 'grid-table rebar-table');
|
||||
|
||||
Reference in new issue
Block a user