Files
Pier_v1/tests/abut-wing-card.test.ts
T
ypshimandClaude Opus 5.5 80e5302192 feat(abut): ② 날개벽 카드를 시안대로 작성 (좌우 같게 · 측별 치수 · 삽도 3개)
- 선택 줄 (타일): 좌우 같게(기본) / 따로 정하기, 편집할 측 (좌 · ① 쪽 / 우 · ⑤ 쪽), 형식 일반형(기본) / 단축형 / 없음
- 측별 치수 입력 (WingDims, 교대 기록 wingDims): 길이 6500, 두께 450, 끝 높이 1500(일반형), 헌치 300. 단축형 길이 기본 = 뒷굽 끝(1.807 m). 좌우 같게이면 양쪽에 함께 적용, 켤 때 좌측 값을 우측에 복사
- 연동: 두께 → 날개벽 3D · 접속슬래브 폭(geometry slabWidthMm) · 슬래브 평면, 헌치 → 연직 헌치 · 저판 상면 헌치 (0이면 없음)
- 자동 값: 경사 (A1 = −i), 등단면 · 변화단면 경계(뒷굽 끝), 끝 EL 상단 / 하단 (가정), 단축형 하단 = 확대기초 상면 (뒷굽 밖은 AI02 안내)
- 삽도 3개 (src/view/abut-wing-svg.ts): 평면(좌우 날개벽 · 접속슬래브, abutPlanOutline 공유), 측면(수평 단 · 뒷굽 끝), 날개벽–흉벽 모서리 모식도(헌치 · 브라켓 · 슬래브 모따기 · 간격)
- "생성되는 날개벽" 줄과 시안 메모. 프로젝트 파일 검사에 wingDims · wingsLinked 추가
- 테스트 7건 추가 · 갱신. 전체 669건 통과, 타입 검사 · 빌드 통과. 브라우저 시각 확인은 미실시

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 09:20:29 +09:00

69 lines
4.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 } from '../src/core/abut/geometry';
import { parseAbutments, toAbutInput } from '../src/core/abut/parse';
import { buildMembers } from '../src/core/abut/members';
import { buildWings } from '../src/core/abut/wings';
import { abutPlanOutline } from '../src/view/abut-scene';
import { abutWingCornerSvg, abutWingPlanSvg, abutWingSideSvg } from '../src/view/abut-wing-svg';
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
const rec = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0];
const build = (r = rec) => {
const model = buildAbut(toAbutInput(r));
const members = buildMembers(model, { bottomEl: r.foundationBottomEl, pileLengthMm: r.pileLengthMm }, r.shape);
return { model, members, wings: buildWings(model, members, r.wings, r.wingDims) };
};
describe('날개벽 치수 입력 (측별) 과 연동', () => {
it('defaults: 6500 · 450 · 1500 · haunch 300 on both sides; 단축형 runs to the 뒷굽 끝 (1.807 m)', () => {
const { wings } = build();
for (const w of wings) expect([w.lengthM, w.thicknessM, w.endHeightM, w.haunchM]).toEqual([6.5, 0.45, 1.5, 0.3]);
const short = build({ ...rec, wings: { left: 'short', right: 'short' } }).wings[0];
expect(short.lengthM).toBeCloseTo(short.heelDistanceM, 3);
expect(short.heelDistanceM).toBeCloseTo(1.807, 3);
});
it('a thicker wing narrows the 접속슬래브 (slab width = L − Σ(t/|sin Δ| + 20))', () => {
const base = build().model.wing!.slabWidthMm;
const thick = build({ ...rec, wingDims: { left: { thicknessMm: 550 }, right: { thicknessMm: 550 } } });
expect(base - thick.model.wing!.slabWidthMm).toBeGreaterThan(195); // 2 × 100 / |sin Δ| ≈ 200.8
expect(thick.wings[0].thicknessM).toBeCloseTo(0.55, 9);
});
it('haunch 0 drops the 연직 헌치 and the 저판 상면 헌치 of that wing', () => {
const w = build({ ...rec, wingDims: { left: { haunchMm: 0 }, right: {} } }).wings.find((x) => x.side === 'left')!;
expect(w.haunches).toHaveLength(0);
expect(w.footingHaunch).toBeUndefined();
});
});
describe('날개벽 삽도 (시안 ② 날개벽 카드)', () => {
it('평면: both wings, the slab between them and its width', () => {
const { model, members, wings } = build();
const svg = abutWingPlanSvg({
lengthM: model.length, outline: abutPlanOutline(model, members, wings), leftLabel: '좌 · 일반형', rightLabel: '우 · 일반형',
leftSEnd: wings.find((w) => w.side === 'left')!.sEnd, slabWidthM: model.wing!.slabWidthMm / 1000,
});
for (const s of ['좌 · 일반형', '우 · 일반형', `접속슬래브 폭 ${(model.wing!.slabWidthMm / 1000).toFixed(3)} m`]) expect(svg).toContain(s);
expect(svg.match(/<polygon /g)).toHaveLength(4); // slab, wall, two wings
});
it('측면: the bands, the length, the end height and the 뒷굽 끝', () => {
const { members, wings } = build();
const w = wings[0];
const svg = abutWingSideSvg({
name: '좌 날개벽', form: w.form, lengthM: w.lengthM, endHeightM: w.endHeightM, heelDistanceM: w.heelDistanceM,
topLine: w.topLine, bottomLine: w.bottomLine, bands: w.bands, seatEl: rec.seatEl, footingTopEl: members.footingTopEl,
});
for (const s of ['6500 (교좌면 끝에서, 날개벽 축 방향)', '1500', '등단면', '변화단면1', '변화단면2', '뒷굽 끝 1.807 m', '교좌면 44.367']) expect(svg).toContain(s);
});
it('모서리: haunch, bracket, slab chamfer and gaps from the inputs', () => {
const svg = abutWingCornerSvg({ parapetMm: 500, bracketMm: 300, wingThicknessMm: 450, haunchMm: 300, slabGapWingMm: 20, slabGapParapetMm: 5, chamferMm: 300 });
for (const s of ['헌치 300 × 300', '브라켓 300 (슬래브 아래)', '슬래브 모따기 300 × 300', '날개벽과 20 · 흉벽과 5', '흉벽 500', '날개벽 450']) expect(svg).toContain(s);
});
});