fix(abut): 접속슬래브를 날개벽 안쪽면 사이에 두어 간섭 제거

- 슬래브 평면을 날개벽 안쪽면에서 20 mm 안쪽으로 잡음 (spec §4.8 "측면 = 날개벽 안쪽면 + 20"). 날개벽은 축 방향 프리즘(두께 0.45 m, s 방향)으로 그리므로 안쪽면 s = sEnd + a·0.45 + (ws/wt)·t
- 배면 끝은 흉벽 배면 −0.505에서 7 m (t 기준)
- 간섭 검사 테스트: 슬래브 꼭짓점이 어느 날개벽 평면 영역에도 들어가지 않음
- 테스트 635건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-03 16:11:53 +09:00
1 parent 8f75c6dc9f
commit fbc315fdba
2 files changed
+54 -17

No files matched your search

+26 -7
View File
@@ -14,7 +14,8 @@ const rec = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abu
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 wings = buildWings(model, members);
const group = buildAbutGroup(model, members, wings);
const named = (name: string) => group.getObjectByName(name) as THREE.Group | undefined;
@@ -23,12 +24,30 @@ describe('교대 3D: 받침블럭과 접속슬래브 (spec §4.2, §4.8)', () =>
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('draws the 접속슬래브 as one slab', () => {
expect(named('slab')?.children).toHaveLength(1);
});
it('the slab does not overlap either wing in plan (spec §4.8: 날개벽 안쪽면 + 20)', () => {
// Axis frame: s along IP1 → IP3, t positive to the 전면, as toAxis() in abut-scene.ts.
const axis = (p: { e: number; n: number }) => {
const d = { e: p.e - model.ip1.e, n: p.n - model.ip1.n };
const f = { e: model.nLeft.e * model.frontSide, n: model.nLeft.n * model.frontSide };
return { s: d.e * model.u.e + d.n * model.u.n, t: d.e * f.e + d.n * f.n };
};
const inside = (x: number, y: number, poly: { s: number; t: number }[]) => {
let c = false;
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
if (poly[i].t > y !== poly[j].t > y && x < ((poly[j].s - poly[i].s) * (y - poly[i].t)) / (poly[j].t - poly[i].t) + poly[i].s) c = !c;
}
return c;
};
const pos = (named('slab') as THREE.Group).children[0] as THREE.Mesh;
const attr = pos.geometry.getAttribute('position');
const polys = wings.map((w) => w.corners.map(axis));
for (let i = 0; i < attr.count; i++) {
for (const poly of polys) expect(inside(attr.getX(i), attr.getZ(i), poly)).toBe(false);
}
});
it('the slab reaches 7 m behind the 흉벽 back face and is 450 thick', () => {