feat(abut): 교대 형상·입력 패널과 교각 받침·솔플레이트 개편

- 교대 형상(src/core/abut, 3D 장면, 목록·카메라·맞춤)과 교대 입력 패널 추가
- 교각 받침 기준 카드 제거, 솔플레이트 규칙을 교각별 입력으로 변경
- 받침 ELEVATION·솔플레이트 TABLE을 모델값으로 연결
- 교대 입력값 카드: 형식 행에 시점·종점 표시, 제목을 "A1 입력값 [입력]"으로 변경
- 테스트 614건 통과, 타입 검사 통과

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-03 14:51:34 +09:00
1 parent b1b2ad1ddb
commit 1d31d2f3a9
44 files changed
+2571 -120

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import * as XLSX from 'xlsx';
import { describe, expect, it } from 'vitest';
import { buildAbut } from '../src/core/abut/geometry';
import { parseAbutments, toAbutInput } from '../src/core/abut/parse';
import { buildMembers } from '../src/core/abut/members';
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
// 운곡교 A1, 기준 입력 파일에서 읽음 (spec_abut §10.1).
const input = toAbutInput(parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0]);
const model = buildAbut(input);
const m = buildMembers(model, { bottomEl: input.foundationBottomEl, pileLengthMm: input.pileLengthMm });
describe('교대 A1 벽체 · 흉벽 · 저판 · 말뚝 (AA3, spec_abut §10.1)', () => {
it('저판 상면 41.995 (기초 하단 40.895 + 두께 1.1)', () => {
expect(m.footingTopEl).toBeCloseTo(41.995, 3);
});
it('wall height ① 구간 2.372 m, five segments with the seat EL of each bearing', () => {
expect(m.wallSegments).toHaveLength(5);
const first = m.wallSegments.find((w) => Math.abs(w.seatEl - 44.367) < 1e-6)!;
expect(first.heightM).toBeCloseTo(2.372, 3);
});
it('wall segments tile the axis from 0 to L with the step boundaries', () => {
const w = m.wallSegments;
expect(w[0].sFrom).toBeCloseTo(0, 9);
expect(w[w.length - 1].sTo).toBeCloseTo(model.length, 9);
expect(w[0].sTo).toBeCloseTo(model.stepBounds[0], 9);
for (let i = 1; i < w.length; i++) expect(w[i].sFrom).toBeCloseTo(w[i - 1].sTo, 9);
});
it('흉벽 상단 EL 47.180 at IP1 end and 46.835 at IP3 end; slope −2.829 %', () => {
expect(m.parapetTopStartEl).toBeCloseTo(47.18, 2);
expect(m.parapetTopEndEl).toBeCloseTo(46.835, 2);
expect(m.parapetSlope * 100).toBeCloseTo(-2.829, 2);
});
it('말뚝 3열 × 6본 = 18, rows at t −1.15 / +0.85 / +2.15, spacing ≈ 2.179 m', () => {
expect(m.piles.map((r) => r.t)).toEqual([-1.15, 0.85, 2.15]);
expect(m.piles.every((r) => r.s.length === 6)).toBe(true);
const gap = m.piles[0].s[1] - m.piles[0].s[0];
expect(gap).toBeCloseTo(2.179, 2);
expect(m.piles[0].s[0]).toBeCloseTo(0.65, 6);
});
it('말뚝 선단 EL 19.295 (40.895 − 21.6 m)', () => {
expect(m.pileBottomEl).toBeCloseTo(19.295, 3);
});
it('without a foundation the footing and piles have no EL (no crash)', () => {
const bare = buildMembers(model);
expect(bare.footingBottomEl).toBeUndefined();
expect(bare.pileBottomEl).toBeUndefined();
expect(bare.piles[0].s).toHaveLength(6);
});
});