- 교대 형상(src/core/abut, 3D 장면, 목록·카메라·맞춤)과 교대 입력 패널 추가 - 교각 받침 기준 카드 제거, 솔플레이트 규칙을 교각별 입력으로 변경 - 받침 ELEVATION·솔플레이트 TABLE을 모델값으로 연결 - 교대 입력값 카드: 형식 행에 시점·종점 표시, 제목을 "A1 입력값 [입력]"으로 변경 - 테스트 614건 통과, 타입 검사 통과 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
140 lines
5.7 KiB
TypeScript
140 lines
5.7 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { buildAbut, type AbutInput } from '../src/core/abut/geometry';
|
||
|
||
// Abut 시트의 A1 (운곡교, 2026-10-30 6차). 시트 x = 북, y = 동 → 내부 (E, N) = (y, x).
|
||
const A1: AbutInput = {
|
||
name: 'A1',
|
||
type: 'A1',
|
||
seatEl: 44.367,
|
||
ip1: { e: 179131.917023, n: 259815.91973 },
|
||
ip2: { e: 179131.907126, n: 259815.91805 },
|
||
ip3: { e: 179119.891533, n: 259817.959709 },
|
||
bearings: [
|
||
{ no: 1, pos: { e: 179121.1404, n: 259818.5035 }, z: 44.492 },
|
||
{ no: 2, pos: { e: 179123.6148, n: 259818.0835 }, z: 44.563 },
|
||
{ no: 3, pos: { e: 179126.0891, n: 259817.6635 }, z: 44.634 },
|
||
{ no: 4, pos: { e: 179128.5634, n: 259817.2435 }, z: 44.705 },
|
||
{ no: 5, pos: { e: 179131.0377, n: 259816.8236 }, z: 44.776 },
|
||
],
|
||
};
|
||
|
||
describe('교대 A1 축과 받침 (AA1, spec_abut §10.1)', () => {
|
||
const m = buildAbut(A1);
|
||
|
||
it('axis IP1 → IP3 is 12.197 m', () => {
|
||
expect(m.length).toBeCloseTo(12.197, 3);
|
||
});
|
||
|
||
it('bearing s from IP1: 11.057 / 8.547 / 6.037 / 3.528 / 1.018 m (①–⑤)', () => {
|
||
const expected = [11.057, 8.547, 6.037, 3.528, 1.018];
|
||
m.bearings.forEach((b, i) => expect(b.s).toBeCloseTo(expected[i], 2));
|
||
});
|
||
|
||
it('seat EL ①–⑤: 44.367 / 44.438 / 44.509 / 44.580 / 44.651 (block 125 mm, pad included)', () => {
|
||
const expected = [44.367, 44.438, 44.509, 44.58, 44.651];
|
||
m.bearings.forEach((b, i) => {
|
||
expect(b.seatEl).toBeCloseTo(expected[i], 3);
|
||
expect(b.blockHeightMm).toBe(125);
|
||
});
|
||
});
|
||
|
||
it('step boundaries from IP1: 2.273 / 4.783 / 7.292 / 9.802 m', () => {
|
||
const expected = [2.273, 4.783, 7.292, 9.802];
|
||
expect(m.stepBounds).toHaveLength(4);
|
||
m.stepBounds.forEach((s, i) => expect(s).toBeCloseTo(expected[i], 2));
|
||
});
|
||
|
||
it('the bearings are on the left of 1 → n for A1 (front), so the layout matches the type (AW03 clear)', () => {
|
||
expect(m.frontLeftOfBearingDir).toBe(true);
|
||
expect(m.layoutMatchesType).toBe(true);
|
||
});
|
||
|
||
it('the bearing 1 lies 0.745 m from the axis (spec §10.1 전면 판정)', () => {
|
||
expect(Math.abs(m.bearings[0].lateral)).toBeCloseTo(0.745, 2);
|
||
});
|
||
|
||
it('an A2 label on the same layout fails the type check (AW03)', () => {
|
||
expect(buildAbut({ ...A1, type: 'A2' }).layoutMatchesType).toBe(false);
|
||
});
|
||
|
||
it('a bearing with its own 교좌면 EL uses it and its block height is z − seat', () => {
|
||
const own = buildAbut({ ...A1, bearings: A1.bearings.map((b, i) => (i === 2 ? { ...b, seatEl: 44.5 } : b)) });
|
||
expect(own.bearings[2].seatEl).toBeCloseTo(44.5, 6);
|
||
expect(own.bearings[2].blockHeightMm).toBe(134);
|
||
expect(own.bearings[0].blockHeightMm).toBe(125);
|
||
});
|
||
|
||
it('refuses an abutment with fewer than two bearings or coincident IP1 and IP3', () => {
|
||
expect(() => buildAbut({ ...A1, bearings: A1.bearings.slice(0, 1) })).toThrow('받침이 2개 이상');
|
||
expect(() => buildAbut({ ...A1, ip3: A1.ip1 })).toThrow('같은 점');
|
||
});
|
||
});
|
||
|
||
// AA2 (spec_abut §4.7, §10.1, §10.2): 날개벽 선, 날개벽 경사, AW01, 접속슬래브 폭.
|
||
const SKEW = 95 + 2 / 60 + 52 / 3600; // 95°02′52″
|
||
const GIRDER = 75 + 56 / 60 + 55.9 / 3600; // 거더 방향 CAD 75°56′55.9″
|
||
const A1_WING: AbutInput = { ...A1, skewDeg: SKEW, slopeI: -0.0191, girderAzimuthDeg: GIRDER };
|
||
|
||
// A2 합성 예제 (spec §10.2): A1을 중심 C 기준으로 180° 돌리고, 받침 번호를 거꾸로 매겨 1 → n 방향이 A1과 같게 한다.
|
||
const A2_WING: AbutInput = {
|
||
name: 'A2',
|
||
type: 'A2',
|
||
seatEl: 44.367,
|
||
ip1: { e: 179161.0318, n: 259932.3259 },
|
||
ip2: { e: 179161.0417, n: 259932.3276 },
|
||
ip3: { e: 179173.0573, n: 259930.2859 },
|
||
bearings: [
|
||
{ no: 1, pos: { e: 179161.9111, n: 259931.422 }, z: 44.492 },
|
||
{ no: 2, pos: { e: 179164.3854, n: 259931.0021 }, z: 44.563 },
|
||
{ no: 3, pos: { e: 179166.8597, n: 259930.5821 }, z: 44.634 },
|
||
{ no: 4, pos: { e: 179169.334, n: 259930.1621 }, z: 44.705 },
|
||
{ no: 5, pos: { e: 179171.8084, n: 259929.7421 }, z: 44.776 },
|
||
],
|
||
skewDeg: SKEW,
|
||
slopeI: -0.0191,
|
||
girderAzimuthDeg: GIRDER,
|
||
};
|
||
|
||
describe('교대 날개벽 · 접속슬래브 폭 (AA2, spec_abut §4.7–4.8, §10)', () => {
|
||
const w = buildAbut(A1_WING).wing!;
|
||
|
||
it('A1 wing line is 255.32° CAD, on the 배면 side (not the bridge side)', () => {
|
||
expect(w.lineDeg).toBeCloseTo(255.32, 1);
|
||
});
|
||
|
||
it('AW01: the wing line is within 1° of the girder direction (0.63°)', () => {
|
||
expect(w.girderDiffDeg).toBeCloseTo(0.63, 1);
|
||
expect(w.aw01Ok).toBe(true);
|
||
});
|
||
|
||
it('A1 wing slope is +1.91 % (A1 = −i), wing rising away from the wall', () => {
|
||
expect(w.slopePct).toBeCloseTo(1.91, 2);
|
||
});
|
||
|
||
it('slab width 11.253 m with both general wings; 11.725 with one; 12.197 with none', () => {
|
||
expect(w.slabWidthMm).toBeCloseTo(11253, -1);
|
||
expect(buildAbut({ ...A1_WING, wings: { left: 'general', right: 'none' } }).wing!.slabWidthMm).toBeCloseTo(11725, -1);
|
||
expect(buildAbut({ ...A1_WING, wings: { left: 'none', right: 'none' } }).wing!.slabWidthMm).toBeCloseTo(12197, -1);
|
||
});
|
||
|
||
it('A2 synthetic: the bearings are on the right of 1 → n, so the layout matches A2 (AW03 clear)', () => {
|
||
const m2 = buildAbut(A2_WING);
|
||
expect(m2.frontLeftOfBearingDir).toBe(false);
|
||
expect(m2.layoutMatchesType).toBe(true);
|
||
expect(m2.bearings[0].s).toBeCloseTo(1.018, 2);
|
||
expect(m2.bearings[4].s).toBeCloseTo(11.057, 2);
|
||
});
|
||
|
||
it('A2 synthetic: the wing line is 75.32° (배면 쪽, 교량 반대쪽), not 255.32° (the wrong 1→n rotation)', () => {
|
||
const w2 = buildAbut(A2_WING).wing!;
|
||
expect(w2.lineDeg).toBeCloseTo(75.32, 1);
|
||
expect(w2.aw01Ok).toBe(true);
|
||
expect(w2.slopePct).toBeCloseTo(-1.91, 2);
|
||
expect(w2.slabWidthMm).toBeCloseTo(11253, -1);
|
||
});
|
||
|
||
it('without skew there is no wing block', () => {
|
||
expect(buildAbut(A1).wing).toBeUndefined();
|
||
});
|
||
});
|