Files
Pier_v1/tests/abut-wings.test.ts
T
ypshimandClaude Haiku 5.5 7fab9b6685 fix(abut): 날개벽·헌치·접속슬래브가 벽체와 흉벽 안으로 들어가지 않도록
- 날개벽: 벽체 배면(t −0.8) 뒤로만 세움. 벽체·흉벽 앞쪽 부분은 잘라냄 (프로파일을 dM ≥ 벽체 배면 위치로 자름)
- 위 헌치: 브라켓 상면이 s 방향으로 기울므로, 헌치 면적 안에서 가장 높은 브라켓 상면 위에 놓음
- 접속슬래브: 흉벽 배면 단차 위(t ≥ 벽체 배면)는 브라켓 상면에 평평하게 얹고, 벽체 뒤쪽에서만 4 % 하향
- 테스트: 날개벽 t ≤ −0.8, 벽체 배면 위치, 위 헌치와 슬래브가 배면 단차 아래로 내려가지 않음. 전체 638건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시

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

159 lines
7.0 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;
// 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(low.zBottomEl).toBeCloseTo(41.995, 3);
expect(low.zTopEl).toBeCloseTo(46.838 - 0.85, 2);
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);
});
});