feat(abut): ② 교량받침·받침블럭·솔플레이트 카드를 시안대로 구현

- 받침 ELEVATION TABLE (위치 s, 구간, 교좌면 EL, h1 75, h2 50, z = EL.X, h3, h4, E, 솔플 상면 EL, A, B, 블럭~계단 경계)과 솔플레이트 상세 TABLE (S, L, W, T, T1, T2, 검산)을 교대 모델에서 계산 (src/core/abut/bearing-table.ts)
- 공통 · 규칙 입력 4개: 받침 높이 h3 181, 솔플레이트 T 30, 솔플 여유 100, 받침 종류 탄성받침. 기본값은 교각 P1 가정 (QA-15). 교대 기록의 bearingRule에 저장, 프로젝트 파일 검사 포함
- 검사 줄: W02, W04, W03, I01
- ②에서는 받침별 z·교좌면 입력칸 대신 표를 보여 줌 (①은 그대로). 삽도는 넣지 않음
- 테스트 5건 추가, 전체 642건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Opus 5.5 committed 2026-11-03 18:14:29 +09:00
1 parent 5bdd360235
commit 7e3ba306f3
8 files changed
+346 -5

No files matched your search

+99 -4
View File
@@ -8,12 +8,21 @@ import { parseAbutments, toAbutInput, type AbutRecord } from './core/abut/parse'
import { buildAbut } from './core/abut/geometry';
import { structureOrder } from './core/abut/order';
import { ABUT_DETAIL_LABELS, ABUT_SEGMENT_HEADER, abutInputRows, abutSegmentRows, abutSummaryRows } from './core/abut/summary';
import { applyAbutEdit, applyBearingEdit, applyShapeEdit, ABUT_EDIT_FIELDS, SHAPE_EDIT_FIELDS, restoreAbutDefault, setWingForm } from './core/abut/edit';
import { applyAbutEdit, applyBearingEdit, applyBearingRule, applyShapeEdit, ABUT_EDIT_FIELDS, BEARING_RULE_FIELDS, SHAPE_EDIT_FIELDS, restoreAbutDefault, setWingForm } from './core/abut/edit';
import { ABUT_SHAPE_DEFAULT, resolveShape } from './core/abut/shape';
import { abutShown, piersShown } from './core/abut/visibility';
import {
ABUT_BEARING_RULE_DEFAULT,
ABUT_BLOCK_MM,
ABUT_PAD_PLAN_MM,
abutBearingChecks,
abutElevationRows,
abutSolplateRows,
resolveBearingRule,
} from './core/abut/bearing-table';
import { abutMemberRows, ABUT_MEMBER_EDITABLE, type AbutMemberId } from './core/abut/member-list';
import type { WingForm } from './core/abut/geometry';
import { buildMembers } from './core/abut/members';
import type { AbutModel, WingForm } from './core/abut/geometry';
import { buildMembers, type AbutMembers } from './core/abut/members';
import { buildWings } from './core/abut/wings';
import { buildAbutGroup, cameraForBox } from './view/abut-scene';
import { fitCamera } from './view/fit';
@@ -748,6 +757,90 @@ function renderAbutMembers(rec: AbutRecord): void {
}
}
/** ② 교량받침·받침블럭·솔플레이트 카드 (시안): 블럭 배치, ELEVATION TABLE, 솔플레이트 TABLE, 공통 · 규칙 입력, 검사 줄. */
function fillAbutBearingCard(
box: HTMLDivElement,
rec: AbutRecord,
apply: (next: AbutRecord) => void,
fail: (err: unknown, input: HTMLInputElement, back: string) => void,
idOf: (suffix: string) => string,
numberInput: (id: string, value: string) => HTMLInputElement,
): void {
const rule = resolveBearingRule(rec.bearingRule);
const table = (head: string[], rows: string[][]) => {
const tbl = document.createElement('table');
tbl.className = 'abut-seg';
const tr = tbl.createTHead().insertRow();
for (const h of head) tr.append(el('th', h));
const body = tbl.createTBody();
for (const row of rows) {
const r = body.insertRow();
for (const cell of row) r.append(el('td', cell));
}
return tbl;
};
box.append(el('p', '블럭 배치: 분리 (교대 일체 블럭은 규칙 미정 — 교좌면이 계단이라 분리 고정)', 'muted small'));
let model: AbutModel;
let members: AbutMembers;
try {
model = buildAbut(toAbutInput(rec));
members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm }, rec.shape);
} catch {
box.append(el('p', '교대 모델을 만들 수 없어 받침 표를 표시하지 않습니다.', 'muted small'));
return;
}
const m3 = (v: number) => v.toFixed(3);
box.append(el('p', '받침 ELEVATION TABLE (mm, EL·위치는 m) · 거리는 IP3(① 쪽)에서 · h3·h4는 가정 (QA-15)', 'lbl'));
box.append(
table(
['No.', '위치 s', '구간', '교좌면 EL', 'h1 블럭', 'h2 패드', 'z = EL.X', 'h3 받침', 'h4 솔플', 'E', '솔플 상면 EL', 'A', 'B', '블럭 ~ 계단 경계'],
abutElevationRows(model, rule).map((r) => [
String(r.no), m3(r.sFromIp3M), r.seg, m3(r.seatEl), String(r.h1), String(r.h2), m3(r.z), String(r.h3), String(r.h4),
String(r.E), m3(r.solplateTopEl), String(r.A), String(r.B), r.stepGapM === null ? '—' : m3(r.stepGapM),
]),
),
);
box.append(el('p', '솔플레이트 상세 TABLE (mm) · 가정 (교각 P1 값)', 'lbl'));
box.append(
table(
['No.', 'S (%)', 'L', 'W', 'T', 'T1', 'T2', '검산 T1 − T2 ≈ L·|S|'],
abutSolplateRows(model, rule, rec.slopeI).map((r) => [String(r.no), r.S.toFixed(3), String(r.L), String(r.W), String(r.T), String(r.T1), String(r.T2), r.check]),
),
);
box.append(el('p', `공통 · 규칙 — 블럭 평면 ${ABUT_BLOCK_MM} × ${ABUT_BLOCK_MM} (기본값) · 몰탈패드 ${ABUT_PAD_PLAN_MM} × ${ABUT_PAD_PLAN_MM} × 50 (기본값) · 블럭 높이 = min(z) − 교좌면 (계산)`, 'muted small'));
for (const f of BEARING_RULE_FIELDS) {
const id = idOf(`rule-${f.key}`);
const lab = el('label', f.label);
lab.htmlFor = id;
const input = numberInput(id, String(rule[f.key]));
if (rule[f.key] !== ABUT_BEARING_RULE_DEFAULT[f.key]) input.classList.add('changed-input');
input.addEventListener('change', () => {
try {
apply(applyBearingRule(rec, f.key, Number(input.value)));
} catch (err) {
fail(err, input, String(rule[f.key]));
}
});
box.append(lab, input, el('span', `기본 ${ABUT_BEARING_RULE_DEFAULT[f.key]} · 가정`, 'muted small'));
}
const kindId = idOf('rule-kind');
const kindLab = el('label', '받침 종류 · 용량');
kindLab.htmlFor = kindId;
const kind = el('input');
kind.id = kindId;
kind.type = 'text';
kind.value = rule.kind;
kind.addEventListener('change', () => {
try {
apply(applyBearingRule(rec, 'kind', kind.value));
} catch (err) {
fail(err, kind, rule.kind);
}
});
box.append(kindLab, kind, el('span', `기본 ${ABUT_BEARING_RULE_DEFAULT.kind}`, 'muted small'));
box.append(el('p', `검사 · ${abutBearingChecks(model, members, rule).join(' · ')}`, 'muted small abut-checks'));
}
/** Shows only the card of the open row in the ② 교대 editor; the basic items stay visible. */
function applyAbutMemberCard(rec: AbutRecord): void {
const member = state.abutMember;
@@ -844,7 +937,9 @@ function fillAbutSlot(slot: (typeof ABUT_SLOTS)[keyof typeof ABUT_SLOTS], rec: A
});
if (!planCard) basic.appendChild(restore);
for (const b of rec.bearings) {
// ① keeps the 받침별 z · 교좌면 inputs; ② shows the 교량받침 카드 (tables, rules, checks) instead (2026-11-03).
if (planCard) fillAbutBearingCard(bearings, rec, apply, fail, idOf, numberInput);
for (const b of planCard ? [] : rec.bearings) {
const zId = idOf(`bz-${b.no}`);
const zLab = el('label', `받침 ${b.no} z (m)`);
zLab.htmlFor = zId;