Files
Pier_v1/tests/abut-solplate-scene.test.ts
ypshimandClaude Opus 5.5 1fda705b75 feat(abut): 교대 3D에 받침 본체 · 솔플레이트 (교각과 같은 개념)
교대 3D는 받침블럭 콘크리트와 몰탈패드만 그렸다. ② 받침 카드와 같은 솔플레이트(abutSoles)로 교각처럼 그린다.
- 받침 본체: 솔플레이트 평면에서 사방 100 mm 뺀 상자, 높이 h3, 패드 상면에서
- 솔플레이트: L은 거더 방향(블럭 회전 = 방위각, 적용한 계산 방위각 포함), W는 그 직각, T1은 오르막 쪽
- 거더 하면 고정: T ≠ T_ref이면 블럭 상면 · 패드가 (T − T_ref)만큼 내려감 (받침 ELEVATION TABLE과 일치)
- 부재 표시: '받침·받침블럭'에 교대 bearings, '솔플레이트'에 교대 solplates 연결
- 테스트: A1(짝 P1) 본체 높이 245 · 솔플 상면 z + 245 + 38, 두꺼운 끝이 오르막(A1) 쪽, S −3 % → 패드 −4 mm ·
  상면 유지, soles 없이 부를 때 기존 형상. 브라우저: 교대만 · 교각+교대 가져오기 예외 0, 받침 쌓임 확인

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:03:57 +09:00

69 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { abutSoles, resolveBearingRule } from '../src/core/abut/bearing-table';
import { buildAbutGroup } from '../src/view/abut-scene';
import { loadInputWorkbook, p1Pairs } from './helpers';
// 교대 받침 본체 · 솔플레이트 3D (2026-11-06, 교각과 같은 개념). A1 with its P1 pair: S −1.908 %, T 30, h3 245.
// Vertex positions are 32-bit floats: at EL 44 m they carry ~1.5 µm, so heights are compared to 5 decimals.
const rec = parseAbutments(loadInputWorkbook()).abutments.find((a) => a.name === 'A1')!;
const model = buildAbut(toAbutInput(rec));
const ctx = { pairs: p1Pairs(), slopeI: rec.slopeI };
const soles = abutSoles(model, resolveBearingRule(), rec.bearingHeightMm, ctx);
const group = buildAbutGroup(model, undefined, undefined, undefined, undefined, undefined, undefined, soles);
const meshes = (name: string) => (group.getObjectByName(name) as THREE.Group).children as THREE.Mesh[];
const box = (m: THREE.Object3D) => new THREE.Box3().setFromObject(m);
describe('교대 받침 본체 · 솔플레이트 3D', () => {
it('one bearing body per bearing, height h3 (245), on the pad top (z)', () => {
expect(meshes('bearings')).toHaveLength(5);
model.bearings.forEach((b, i) => {
const bb = box(meshes('bearings')[i]);
expect(bb.max.y - bb.min.y).toBeCloseTo(0.245, 5);
expect(bb.min.y).toBeCloseTo(b.z, 5);
});
});
it('one solplate wedge per bearing: from z + h3 up to T1 (38 mm) at its thick end', () => {
expect(meshes('solplates')).toHaveLength(5);
model.bearings.forEach((b, i) => {
const bb = box(meshes('solplates')[i]);
expect(bb.min.y).toBeCloseTo(b.z + 0.245, 5);
expect(bb.max.y).toBeCloseTo(b.z + 0.245 + 0.038, 5);
});
});
it('T1 sits on the 오르막 쪽: A1 is higher than P1, so the thick end points away from P1 (against the azimuth)', () => {
model.bearings.forEach((b, i) => {
const pos = meshes('solplates')[i].geometry.getAttribute('position') as THREE.BufferAttribute;
const pts = Array.from({ length: pos.count }, (_, k) => new THREE.Vector3(pos.getX(k), pos.getY(k), pos.getZ(k)));
const topY = Math.max(...pts.map((p) => p.y));
const thick = pts.filter((p) => topY - p.y < 1e-6), thin = pts.filter((p) => p.y > b.z + 0.245 + 1e-6 && topY - p.y > 1e-3);
const mid = (ps: THREE.Vector3[]) => ps.reduce((a, p) => a.add(p), new THREE.Vector3()).multiplyScalar(1 / ps.length);
const d = mid(thick).sub(mid(thin));
// Group without a pivot: local x = s along u, local z = t along the front (nLeft · frontSide).
const front = { e: model.nLeft.e * model.frontSide, n: model.nLeft.n * model.frontSide };
const dir = { e: d.x * model.u.e + d.z * front.e, n: d.x * model.u.n + d.z * front.n };
const az = (b.azimuthDeg! * Math.PI) / 180;
expect(dir.e * Math.sin(az) + dir.n * Math.cos(az)).toBeLessThan(0);
});
});
it('거더 하면 고정: S −3 % typed → T 34, the pad and block top drop 4 mm and the solplate top stays', () => {
const steep = abutSoles(model, resolveBearingRule({ slopePct: -3 }), rec.bearingHeightMm, ctx);
const g = buildAbutGroup(model, undefined, undefined, undefined, undefined, undefined, undefined, steep);
const body = box((g.getObjectByName('bearings') as THREE.Group).children[0]);
expect(body.min.y).toBeCloseTo(model.bearings[0].z - 0.004, 5);
// Centre thickness T: the top at the middle of the wedge is pad top + h3 + T, the same as with T 30.
expect(body.min.y + 0.245 + 0.034).toBeCloseTo(model.bearings[0].z + 0.245 + 0.03, 5);
});
it('without soles the group keeps only the block and pad (old callers)', () => {
const plain = buildAbutGroup(model);
expect(plain.getObjectByName('bearings')).toBeUndefined();
expect(plain.getObjectByName('solplates')).toBeUndefined();
});
});