- 배근 결과(abutFootingRebar)로 저판 철근을 교대 좌표에 그림: B1 ⊓ · B2 ⊔ · B3 · B4, B5 ⊓ · B6 ⊔ · B7 · B8, B9, B10-1 · B10-2, 옵션 ② B1-S · B2-S - 교대 선택 시 ③ 철근(rebar3d)으로 사용: 철근표 · 삽도 선택, 선택만 보기, 색 기준, 3D 클릭 선택 공통 - 교각 rebar3d의 강조 · 색 · 범례를 rebarControls로 공용화 - 테스트: 철근표 개수 = 3D 개수, 모든 철근이 저판 안 (skew · 교대축 직각) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
67 lines
3.2 KiB
TypeScript
67 lines
3.2 KiB
TypeScript
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 { abutFootingGeom, abutFootingRebar } from '../src/core/abut/rebar/footing';
|
|
import { skewAngle } from '../src/core/abut/rebar/skew-layout';
|
|
import { abutSkewK } from '../src/view/abut-scene';
|
|
import { buildAbutFootingRebar3D } from '../src/view/abut-rebar3d';
|
|
|
|
// 예제 A1 (교대확대기초철근.md §13).
|
|
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
|
|
const rec = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0];
|
|
const model = buildAbut(toAbutInput(rec));
|
|
const members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm });
|
|
const geom = abutFootingGeom(model.length, members.shape, skewAngle(rec.skewDeg));
|
|
const k = abutSkewK(model);
|
|
const back = members.shape.footingBackM;
|
|
const bottom = members.footingBottomEl!;
|
|
const build = (dir: 'skew' | 'perp') => {
|
|
const r = abutFootingRebar(geom, { dir });
|
|
return { r, scene: buildAbutFootingRebar3D(geom, r, k, back, bottom) };
|
|
};
|
|
// Segments per bar: ⊓ / ⊔ have 3, the others 1.
|
|
const PIECES: Record<string, number> = { B1: 3, B2: 3, B5: 3, B6: 3 };
|
|
|
|
describe('교대 저판 3D 철근 (③, 2026-11-04)', () => {
|
|
for (const dir of ['skew', 'perp'] as const) {
|
|
it(`draws every 철근표 bar with its count (${dir})`, () => {
|
|
const { r, scene } = build(dir);
|
|
for (const b of r.bars) expect(scene.byName.get(b.mark)?.length, b.mark).toBe(b.count * (PIECES[b.mark] ?? 1));
|
|
});
|
|
|
|
it(`keeps every bar inside the 저판 (${dir})`, () => {
|
|
const { scene } = build(dir);
|
|
const top = bottom + geom.H / 1000;
|
|
const front = back + geom.W / 1000;
|
|
const box = new THREE.Box3();
|
|
for (const mesh of scene.group.children as THREE.Mesh[]) {
|
|
mesh.geometry.computeBoundingBox();
|
|
box.copy(mesh.geometry.boundingBox!);
|
|
const tMid = (box.min.z + box.max.z) / 2;
|
|
const s0 = k * tMid;
|
|
expect(box.min.y).toBeGreaterThan(bottom);
|
|
expect(box.max.y).toBeLessThan(top);
|
|
expect(box.min.z).toBeGreaterThan(back);
|
|
expect(box.max.z).toBeLessThan(front);
|
|
// Along the axis, within the skew ends at that t (a bar diameter of slack for the cylinder's sides).
|
|
expect(box.min.x).toBeGreaterThan(s0 + Math.min(0, k * (box.min.z - tMid), k * (box.max.z - tMid)) - 0.03);
|
|
expect(box.max.x).toBeLessThan(s0 + geom.L / 1000 + Math.max(0, k * (box.min.z - tMid), k * (box.max.z - tMid)) + 0.03);
|
|
}
|
|
});
|
|
}
|
|
|
|
it('dims or hides the bars not selected (선택만 보기)', () => {
|
|
const { scene } = build('skew');
|
|
scene.show(new Set(['B9']), true);
|
|
expect(scene.byName.get('B9')!.every((m) => m.visible)).toBe(true);
|
|
expect(scene.byName.get('B1')!.some((m) => m.visible)).toBe(false);
|
|
scene.show(null, false);
|
|
expect(scene.byName.get('B1')!.every((m) => m.visible)).toBe(true);
|
|
});
|
|
});
|