import { describe, expect, it } from 'vitest'; import * as THREE from 'three'; import { buildPier } from '../src/core/geometry'; import { buildRebar3D } from '../src/view/rebar3d'; import { loadP1 } from './helpers'; const rb = buildRebar3D(buildPier(loadP1())); describe('3D 철근 모델 (③)', () => { it('every 철근표 family has bars, named as in the table', () => { for (const n of ['C1', 'C4', 'C7', 'C9', 'C10', 'C11', 'C12', 'C13', 'P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'B1', 'B4', 'B5', 'B6', 'B7', 'B8', 'S1', 'S2']) { expect(rb.byName.get(n)?.length ?? 0, n).toBeGreaterThan(0); } }); it('B1–B4 and B5–B8 all exist in 3D (B2 was missing)', () => { for (const n of ['B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8']) { expect(rb.byName.get(n)?.length ?? 0, n).toBeGreaterThan(0); } }); it('B2 legs reach the B1 height (1873 above the footing bottom), B1 legs hang 304.8 below B1', () => { const f = buildPier(loadP1()).footings[0]; const box = (name: string) => { const b = new THREE.Box3(); for (const mesh of rb.byName.get(name)!) b.expandByObject(mesh); return b; }; expect(box('B2').max.y - f.bottom).toBeCloseTo(1.873, 2); expect(box('B1').min.y - f.bottom).toBeCloseTo(1.873 - 0.3048, 2); }); it('C12 / C13 in 3D: per row two 일자 and two ㄷ (body + 2 legs each end)', () => { expect(rb.byName.get('C12')!.length).toBe(5 * (2 + 2 * 3)); expect(rb.byName.get('C13')!.length).toBe(2 * (2 + 2 * 3)); }); it('C1 legs turn down 472 at the ends (to EL 42.812 at the end face)', () => { const model = buildPier(loadP1()); const b = new THREE.Box3(); for (const mesh of rb.byName.get('C1')!) b.expandByObject(mesh); expect(b.min.y).toBeCloseTo(model.copingRefTop - 0.127 - 0.472, 2); }); it('C9 / C10 / C11: three 1740-wide 폐합 per position, each a U plus a cap (18 pieces each)', () => { expect(rb.byName.get('C10')!.length).toBe(9 * 18); expect(rb.byName.get('C9')!.length).toBe(18 * 18); expect(rb.byName.get('C11')!.length).toBe(18 * 18); }); it('C7 / C8 follow the underside: lowest at the Level (+127 − r), vertical legs up to the C1 level', () => { const model = buildPier(loadP1()); const b = new THREE.Box3(); for (const n of ['C7', 'C8']) for (const mesh of rb.byName.get(n)!) b.expandByObject(mesh); const r = 28.6 / 2000; expect(b.min.y).toBeCloseTo(model.levelBottom + 0.127 - r, 2); // Vertical legs end flat, so no radius on top. C1 is the top row at copingRefTop − 0.127. expect(b.max.y).toBeCloseTo(model.copingRefTop - 0.127, 2); }); it('P1 has one bar per 원주 등분 (46) plus its outward hook tail, P2 one per 등분 (46)', () => { expect(rb.byName.get('P1')!.length).toBe(46 * 2); expect(rb.byName.get('P2')!.length).toBe(46); }); it('P6 crossties in 3D: 8 levels × 4 bars each with main bar + 2 downward hooks (3 pieces per bar = 96 pieces)', () => { expect(rb.byName.get('P6')!.length).toBe(8 * 4 * 3); }); it('couplers in 3D: main bar couplers (368) and tie couplers (35: P5 19 + P4 16)', () => { expect(rb.byName.get('주철근 커플러')!.length).toBe(368); expect(rb.byName.get('띠철근 커플러')!.length).toBe(35); }); it('highlight dims the other bars and keeps the chosen one', () => { rb.highlight('P3'); const p3 = (rb.byName.get('P3')![0].material as THREE.MeshStandardMaterial).opacity; const c1 = (rb.byName.get('C1')![0].material as THREE.MeshStandardMaterial).opacity; expect(p3).toBe(1); expect(c1).toBeLessThan(1); rb.highlight(null); expect((rb.byName.get('C1')![0].material as THREE.MeshStandardMaterial).opacity).toBe(1); }); }); describe('받침블럭 S1 · S2 in 3D (받침블럭철근.md §3–5)', () => { it('per block: 8 S1 and 8 S2, each a bar with two 700 legs (3 pieces)', () => { const blocks = buildPier(loadP1()).blocks.length; expect(rb.byName.get('S1')!.length).toBe(blocks * 8 * 3); expect(rb.byName.get('S2')!.length).toBe(blocks * 8 * 3); }); }); describe('확대기초 B9 · B10 · B11 · B12 in 3D (확대기초철근 (1).md §7–9)', () => { it('B9: 306 vertical bars (column zone removed), each three pieces (bar + two hook tails)', () => { expect(rb.byName.get('B9')!.length).toBe(306 * 3); }); it('B10-1 / B10-2: 18 each (9 levels × 2 faces), each three pieces; B11 60, B12 47', () => { expect(rb.byName.get('B10-1')!.length).toBe(18 * 3); expect(rb.byName.get('B10-2')!.length).toBe(18 * 3); expect(rb.byName.get('B11')!.length).toBe(60 * 3); expect(rb.byName.get('B12')!.length).toBe(47 * 3); }); });