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

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-11-03 14:51:34 +09:00

157 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import * as XLSX from 'xlsx';
import { describe, expect, it } from 'vitest';
import { buildAbut, type AbutInput } from '../src/core/abut/geometry';
import { parseAbutments, toAbutInput } from '../src/core/abut/parse';
import { buildMembers } from '../src/core/abut/members';
import { buildWings, WING_LENGTH_M } from '../src/core/abut/wings';
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
const a1Input = toAbutInput(parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0]);
const SKEW = 95 + 2 / 60 + 52 / 3600;
const GIRDER = 75 + 56 / 60 + 55.9 / 3600;
// A2 합성 예제 (spec §10.2): ① 쪽이 IP1 끝.
const a2Input: 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,
foundationBottomEl: 40.895,
pileLengthMm: 21600,
};
describe('교대 A2 합성 · 날개벽 (AA3b, spec_abut §10.2)', () => {
const model = buildAbut(a2Input);
const members = buildMembers(model, { bottomEl: 40.895, pileLengthMm: 21600 });
const [left, right] = buildWings(model, members);
it('좌 날개벽 (① 쪽, IP1 끝): top 46.838 at the start, 46.714 at the end, bottom end 45.214', () => {
expect(left.side).toBe('left');
expect(left.sEnd).toBeCloseTo(0, 9);
expect(left.topStartEl).toBeCloseTo(46.838, 2);
expect(left.topEndEl).toBeCloseTo(46.714, 2);
expect(left.bottomEndEl).toBeCloseTo(45.214, 2);
});
it('우 날개벽 (⑤ 쪽, IP3 끝): top 47.183 → 47.059, bottom end 45.559', () => {
expect(right.side).toBe('right');
expect(right.sEnd).toBeCloseTo(model.length, 9);
expect(right.topStartEl).toBeCloseTo(47.183, 2);
expect(right.topEndEl).toBeCloseTo(47.059, 2);
expect(right.bottomEndEl).toBeCloseTo(45.559, 2);
});
it('the wing is 6.5 m long along the wing line and 0.45 m thick inward', () => {
const [p0, p1] = left.corners;
expect(Math.hypot(p1.e - p0.e, p1.n - p0.n)).toBeCloseTo(WING_LENGTH_M, 6);
const [, , q2, q3] = left.corners;
expect(Math.hypot(q2.e - p1.e, q2.n - p1.n)).toBeCloseTo(0.45, 6);
expect(Math.hypot(q3.e - p0.e, q3.n - p0.n)).toBeCloseTo(0.45, 6);
});
it('the wing starts at the axis end with the wing direction on the 배면 side (AA2 line 75.32°)', () => {
const [p0, p1] = left.corners;
const deg = (Math.atan2(p1.n - p0.n, p1.e - p0.e) * 180) / Math.PI;
const cad = ((deg % 360) + 360) % 360;
expect(cad).toBeCloseTo(75.32, 1);
});
it('a 단축형 wing bottom sits at the footing top (41.995)', () => {
const short = buildWings(model, members, { left: 'short', right: 'none' });
expect(short).toHaveLength(1);
expect(short[0].bottomEndEl).toBeCloseTo(41.995, 3);
});
it('no wing when the side is 없음', () => {
expect(buildWings(model, members, { left: 'none', right: 'none' })).toEqual([]);
});
});
describe('교대 A1 날개벽 (운곡교P1입력.xlsx)', () => {
const model = buildAbut(a1Input);
const members = buildMembers(model, { bottomEl: a1Input.foundationBottomEl, pileLengthMm: a1Input.pileLengthMm });
const wings = buildWings(model, members);
it('A1 has two general wings; ① 쪽 wing starts at the IP3 end (the bearing ① is near IP3)', () => {
expect(wings.map((w) => w.side)).toEqual(['left', 'right']);
const left = wings[0];
expect(left.sEnd).toBeCloseTo(model.length, 9);
expect(left.topStartEl).toBeCloseTo(46.835, 2);
// A1 날개벽 경사는 +1.91 % (벽체에서 멀어질수록 높아짐): 끝이 0.124 m 높다.
expect(left.topEndEl - left.topStartEl).toBeCloseTo(0.124, 2);
});
});
describe('교대 날개벽 하단 선 · 헌치 (AA3c, spec_abut §4.7)', () => {
const a1 = buildAbut(a1Input);
const a1m = buildMembers(a1, { bottomEl: a1Input.foundationBottomEl, pileLengthMm: a1Input.pileLengthMm });
const [a1Left] = buildWings(a1, a1m);
const a2 = buildAbut(a2Input);
const a2m = buildMembers(a2, { bottomEl: 40.895, pileLengthMm: 21600 });
const [a2Left] = buildWings(a2, a2m);
it('뒷굽 끝까지의 날개벽 거리 1.807 m (단축형 표와 같다)', () => {
expect(a1Left.heelDistanceM).toBeCloseTo(1.807, 2);
expect(a2Left.heelDistanceM).toBeCloseTo(1.807, 2);
});
it('하단선: 뒷굽 끝까지 저판 상면 41.995, 그 밖은 단부 하단까지 직선 (A2 좌: 45.214)', () => {
const line = a2Left.bottomLine;
expect(line[0].el).toBeCloseTo(41.995, 3);
expect(line[1].dM).toBeCloseTo(1.807, 2);
expect(line[1].el).toBeCloseTo(41.995, 3);
expect(line[2].el).toBeCloseTo(45.214, 2);
});
it('two 300 × 300 haunches: upper at the 흉벽 배면, lower at the 벽체 배면, legs 0.3 m', () => {
expect(a2Left.haunches.map((h) => h.kind)).toEqual(['upper', 'lower']);
const [c0, c1, c2] = a2Left.haunches[0].corners;
expect(Math.hypot(c1.e - c0.e, c1.n - c0.n)).toBeCloseTo(0.3, 6);
expect(Math.hypot(c2.e - c0.e, c2.n - c0.n)).toBeCloseTo(0.3, 6);
});
it('upper haunch runs from the bracket top (흉벽 상단 − 0.85) to the wing top; lower from the footing top to the bracket top', () => {
const [up, low] = a2Left.haunches;
expect(up.zBottomEl).toBeCloseTo(46.838 - 0.85, 2);
expect(low.zBottomEl).toBeCloseTo(41.995, 3);
expect(low.zTopEl).toBeCloseTo(up.zBottomEl, 6);
expect(up.zTopEl).toBeGreaterThan(up.zBottomEl);
});
});
describe('날개벽 수평 단 (QA-12 초안 적용, spec_abut §4.7)', () => {
const a2 = buildAbut(a2Input);
const a2m = buildMembers(a2, { bottomEl: 40.895, pileLengthMm: 21600 });
const [a2Left] = buildWings(a2, a2m);
it('등단면 down to 1.5 m below the wing end, then 변화단면1 to the seat EL and 변화단면2 to the footing top', () => {
const [equal, v1, v2] = a2Left.bands;
expect(equal.name).toBe('등단면');
expect(equal.zBottomEl).toBeCloseTo(45.214, 2);
expect(v1.name).toBe('변화단면1');
expect(v1.zBottomEl).toBeCloseTo(44.367, 3);
expect(v2.name).toBe('변화단면2');
expect(v2.zBottomEl).toBeCloseTo(41.995, 3);
});
it('bands are contiguous from the wing top to the footing top', () => {
const b = buildWings(a2, a2m)[0].bands;
for (let i = 1; i < b.length; i++) expect(b[i].zTopEl).toBeCloseTo(b[i - 1].zBottomEl, 6);
expect(b[b.length - 1].zBottomEl).toBeCloseTo(41.995, 3);
});
});