feat(abut-rebar): ③ 교대 벽체 3D 철근 (W1–W11)

- 벽체 배근 경로를 저판 철근과 같은 교대 3D 철근에 추가 (buildAbutRebar3D). 선택 · 선택만 보기 · 색 기준 공통
- 테스트: 철근표 = 3D 개수, 모든 벽체 철근이 벽체 면 · 끝면 안, 저판 하면 철근망 위

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-04 16:31:59 +09:00
1 parent a17c9ebbb4
commit a4c7bcabcf
3 files changed
+56 -10

No files matched your search

+36 -2
View File
@@ -9,7 +9,8 @@ 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';
import { buildAbutRebar3D } from '../src/view/abut-rebar3d';
import { abutWallGeom, abutWallRebar } from '../src/core/abut/rebar/wall';
// 예제 A1 (교대확대기초철근.md §13).
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
@@ -22,7 +23,7 @@ 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) };
return { r, scene: buildAbutRebar3D(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 };
@@ -64,3 +65,36 @@ describe('교대 저판 3D 철근 (③, 2026-11-04)', () => {
expect(scene.byName.get('B1')!.every((m) => m.visible)).toBe(true);
});
});
describe('교대 벽체 3D 철근 (③, 2026-11-04)', () => {
const footing = abutFootingRebar(geom);
const wGeom = abutWallGeom(model.length, members, skewAngle(rec.skewDeg));
const wall = abutWallRebar(wGeom, footing);
const scene = buildAbutRebar3D(geom, footing, k, back, bottom, wall);
it('draws every wall bar of the 철근표 (one tube per straight piece)', () => {
for (const b of wall.bars) {
const pieces = wall.paths.filter((p) => p.mark === b.mark).reduce((a, p) => a + p.pts.length - 1, 0);
expect(scene.byName.get(b.mark)?.length, b.mark).toBe(pieces);
}
expect(scene.byName.get('B1')?.length).toBeGreaterThan(0); // the 저판 bars stay
});
it('keeps the wall bars between the wall faces and the end faces, above the 저판 하면 철근망', () => {
const top = bottom + (wGeom.HF + Math.max(...wGeom.segments.map((s) => s.H)) + wall.ld.H19) / 1000;
const box = new THREE.Box3();
for (const b of wall.bars) {
for (const mesh of scene.byName.get(b.mark)!) {
mesh.geometry.computeBoundingBox();
box.copy(mesh.geometry.boundingBox!);
expect(box.min.z, b.mark).toBeGreaterThan(wGeom.xb / 1000 - 0.3); // 갈고리 296 바깥쪽까지
expect(box.max.z, b.mark).toBeLessThan(wGeom.xf / 1000 + 0.3);
expect(box.min.y, b.mark).toBeGreaterThan(bottom + footing.z.B68 / 1000);
expect(box.max.y, b.mark).toBeLessThan(top + 0.01);
const t = (box.min.z + box.max.z) / 2;
expect(box.min.x, b.mark).toBeGreaterThan(k * t - 0.05 - Math.abs(k) * 0.3);
expect(box.max.x, b.mark).toBeLessThan(k * t + geom.L / 1000 + 0.05 + Math.abs(k) * 0.3);
}
}
});
});