feat(abut): 교대 솔플레이트도 2026-11-06 결정으로 (짝 받침 S · T 하나 · 거더 하면 고정)

교대 받침 카드(spec_abut §5, QA-15)를 교각과 같은 계산(sole-plate.ts)으로 바꾸고 C안 모듈을 정리한다.
- S: 짝 받침(A1 ↔ P1 전열, A2 ↔ Pn) 계산, 없으면 교대 종단경사 i(엑셀 Abut). 직접 수정 가능
- L = A + 100, W = B + 100 (A × B 기본 700 × 640, P1 전열 값 가정), 받침 1열이라 T 하나
- h3 기본값 = 받침 시트 받침높이 − T_ref (A1 275 − 30 = 245): 엑셀 받침높이의 거더 하면을 지킴.
  시안은 P1의 h3 181을 가정했으나 교대 시트에 받침높이가 있어 이렇게 둔다 (입력으로 바꿀 수 있음)
- 블럭 콘크리트 = 블럭 높이 − 패드 − (T − T_ref), E는 그대로
- 입력 칸: h3 · T_ref · T_min · 여유 · A · B · T · S (h3·T·S는 비우면 자동). C안 값(lengthMm 등)은 무시
- A1 검증: S −1.908 … −1.902 % (짝 P1 전열), L 800 · W 740 · T 30 · T1 38 · T2 22, E 400
- src/core/solplate-rule.ts(C안)와 그 테스트 삭제, 솔플레이트 기하 테스트는 새 계획으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Opus 5.5 committed 2026-11-06 14:52:27 +09:00
1 parent a0b0929e13
commit 87b445965b
7 files changed
+184 -205

No files matched your search

+22 -24
View File
@@ -1,42 +1,40 @@
import { describe, expect, it } from 'vitest';
import { buildPier } from '../src/core/geometry';
import { solplateGeometry } from '../src/core/solplate';
import { SOLPLATE_RULE_DEFAULT, solplatePlan } from '../src/core/solplate-rule';
import { loadP1 } from './helpers';
import { loadP1, p1Pairs } from './helpers';
const model = buildPier(loadP1());
// P1 with its A1 girder pairs (2026-11-06): S −1.908 %, L 800 × W 740, T 30, T1 38 / T2 22, h3 181.
const model = buildPier(loadP1(), { pairs: p1Pairs() });
const front1 = model.blocks.filter((b) => b.row === '전열')[0];
const plan = solplatePlan(SOLPLATE_RULE_DEFAULT);
describe('솔플레이트 기하 (C안, 2026-11-03)', () => {
it('L = 교직 1000 across the 교각축 직각 (n) and W = 교축 1100 along the 교축 (s)', () => {
const g = solplateGeometry(front1, plan);
expect(g.L).toBeCloseTo(1.0, 6);
expect(g.W).toBeCloseTo(1.1, 6);
describe('솔플레이트 기하 (결정 2026-11-06)', () => {
it('L = A + 100 = 800 along the girder and W = B + 100 = 740 across it', () => {
const g = solplateGeometry(front1, front1.plan);
expect(g.L).toBeCloseTo(0.8, 6);
expect(g.W).toBeCloseTo(0.74, 6);
});
it('the thick end T1 is on the (−) side: T1 = 27.5 mm, T2 = 22.5 mm, and T1 + T2 = 2T', () => {
const g = solplateGeometry(front1, plan);
expect(g.T1 * 1000).toBeCloseTo(27.5, 6);
expect(g.T2 * 1000).toBeCloseTo(22.5, 6);
expect((g.T1 + g.T2) * 1000).toBeCloseTo(50, 6);
// The top at the (−) corners is the thick side.
const topAtStart = g.corners.filter((c) => Math.abs(c[1] - (g.bottom + g.T1)) < 1e-9);
expect(topAtStart.length).toBe(2);
it('T1 = 38 mm on one end, T2 = 22 mm on the other, and T1 + T2 = 2T', () => {
const g = solplateGeometry(front1, front1.plan);
expect(g.T1 * 1000).toBeCloseTo(38, 6);
expect(g.T2 * 1000).toBeCloseTo(22, 6);
expect((g.T1 + g.T2) * 1000).toBeCloseTo(60, 6);
const thick = g.corners.filter((c) => Math.abs(c[1] - (g.bottom + g.T1)) < 1e-9);
expect(thick.length).toBe(2);
});
it('the underside is the bearing top: block top (EL.X) + h3 (186 mm)', () => {
const g = solplateGeometry(front1, plan);
expect(g.bottom).toBeCloseTo(front1.padTop + 0.186, 6);
it('the underside is the bearing top: pad top (EL.X) + h3 (181 mm)', () => {
const g = solplateGeometry(front1, front1.plan);
expect(g.bottom).toBeCloseTo(front1.padTop + 0.181, 6);
});
it('volume = L·W·T (the taper averages to the centre thickness)', () => {
const g = solplateGeometry(front1, plan);
expect(g.volume).toBeCloseTo(g.L * g.W * 0.025, 9);
it('volume = L·W·T (the taper averages to the row thickness)', () => {
const g = solplateGeometry(front1, front1.plan);
expect(g.volume).toBeCloseTo(g.L * g.W * 0.03, 9);
});
it('the solplate is centred on the block and follows the row direction', () => {
const g = solplateGeometry(front1, plan);
const g = solplateGeometry(front1, front1.plan);
const c = g.corners.reduce((acc, p) => [acc[0] + p[0] / 8, acc[1] + p[2] / 8] as [number, number], [0, 0]);
expect(c[0]).toBeCloseTo(front1.s, 6);
expect(c[1]).toBeCloseTo(front1.n, 6);