import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import * as XLSX from 'xlsx'; import * as THREE from 'three'; 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 { buildAbutGroup } from '../src/view/abut-scene'; const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url)); const rec = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0]; const input = toAbutInput(rec); const model = buildAbut(input); const members = buildMembers(model, { bottomEl: input.foundationBottomEl, pileLengthMm: input.pileLengthMm }); const group = buildAbutGroup(model, members, buildWings(model, members)); const named = (name: string) => group.getObjectByName(name) as THREE.Group | undefined; describe('교대 3D: 받침블럭과 접속슬래브 (spec §4.2, §4.8)', () => { it('has a block and a pad for each of the 5 bearings', () => { expect(named('blocks')?.children).toHaveLength(2 * model.bearings.length); }); it('draws the 접속슬래브 as one slab over the wing-to-wing width', () => { const slab = named('slab'); expect(slab?.children).toHaveLength(1); const box = new THREE.Box3().setFromObject(slab as THREE.Group); const width = box.max.x - box.min.x; expect(width).toBeCloseTo((model.wing?.slabWidthMm ?? 0) / 1000, 2); }); it('the slab reaches 7 m behind the 흉벽 back face and is 450 thick', () => { const box = new THREE.Box3().setFromObject(named('slab') as THREE.Group); expect(box.min.z).toBeCloseTo(members.shape.parapetBackM - 0.005 - 7, 2); expect(box.max.y - box.min.y).toBeGreaterThan(0.45); }); });