Files
Pier_v1/tests/abut-wings.test.ts
T
ypshimandClaude Haiku 5.5 ea36df58cc fix(abut): 뒷굽 헌치를 삽도대로 날개벽과 확대기초 사이에 둠
- 벽체 배면 쪽 아래 헌치를 삭제 (삽도에 근거 없음)
- 뒷굽 헌치 추가: 뒷굽 끝 H = (저판 뒷굽, 저판 상면)에서 날개벽 하면을 따라 0.3 m, 뒷굽 면을 따라 아래로 0.3 m인 (t, EL) 직각삼각형을 날개벽 두께 0.45 m만큼 s 방향으로 밈. 단축형 날개벽에는 없음
- 위 헌치(흉벽 배면)는 그대로
- 테스트 갱신. 전체 638건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시

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

162 lines
7.2 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('the upper 300 × 300 haunch sits at the 흉벽 배면 (legs 0.3 m); the 뒷굽 haunch is a (t, EL) triangle with 0.3 m legs at the heel', () => {
expect(a2Left.haunches.map((h) => h.kind)).toEqual(['upper']);
const tri = a2Left.heelHaunch!;
expect(tri).toHaveLength(3);
expect(tri[0][0]).toBeCloseTo(-1.8, 6); // 뒷굽 끝 (t)
expect(Math.hypot(tri[1][0] - tri[0][0], tri[1][1] - tri[0][1])).toBeCloseTo(0.3, 6);
expect(tri[2][1]).toBeCloseTo(tri[0][1] - 0.3, 6); // leg down the 뒷굽 face
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', () => {
const [up] = a2Left.haunches;
// The upper haunch rests on the highest point of the 브라켓 top over its footprint (the top falls along s), never below it.
expect(up.zBottomEl).toBeGreaterThanOrEqual(46.838 - 0.85 - 1e-6);
expect(up.zBottomEl).toBeLessThan(46.838 - 0.85 + 0.05);
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);
});
});