From 1d8746d5332568c12d6dacb556dfb85707d5671d Mon Sep 17 00:00:00 2001 From: ypshim Date: Fri, 23 Oct 2026 14:42:31 +0900 Subject: [PATCH] =?UTF-8?q?feat(rebar):=20solplate=20geometry=20(pure)=20?= =?UTF-8?q?=E2=80=94=20tapered=20along=20the=20=EA=B5=90=EC=B6=95,=20thick?= =?UTF-8?q?=20end=20T1=20on=20the=20=EC=8B=9C=EC=A0=90=20side?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per bearing: L = A + margin along the 교축 (s), W = B + margin across it; the underside is the bearing top (block top EL.X + h3); the top tapers from T1 = T + L·|S|/200 at the 시점 side (−s) to T2 = T − L·|S|/200 at the 종점 side, so T1 + T2 = 2T and the volume is L·W·T. Corners follow the bearing row (rowSkew). Tests cover L, W, T1, T2, the underside, the volume and the centring. The model and the 3D do not use it yet. Co-Authored-By: Claude Haiku 5.5 --- src/core/solplate.ts | 55 ++++++++++++++++++++++++++++++++++++++++++ tests/solplate.test.ts | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/core/solplate.ts create mode 100644 tests/solplate.test.ts diff --git a/src/core/solplate.ts b/src/core/solplate.ts new file mode 100644 index 0000000..36c394d --- /dev/null +++ b/src/core/solplate.ts @@ -0,0 +1,55 @@ +/** + * 솔플레이트 기하 (1단계, 승인 2026-10-23). Pure: the solplate of one bearing, from its block (position, rowSkew) and the + * 공통 규칙 values. The solplate sits on the bearing (h3) and tapers along the 교축 방향 (s): the thick end T1 is on the + * 시점 side (s = −L/2), the thin end T2 on the 종점 side (s = +L/2). Lengths in m, as in the model. + */ +import { blockFramePoint, type BlockModel } from './geometry'; + +export interface SolplateParams { + /** 받침 평면 A (mm, along 교축 s) and B (mm, along the 교각축 직각 n). */ + A: number; + B: number; + /** 솔플 여유 (mm): L = A + margin, W = B + margin. */ + margin: number; + /** 중심 두께 T (mm). */ + T: number; + /** 기울기 S (%), negative as drawn: thickness changes by L·|S|/2 from the centre. */ + slopePct: number; +} + +export interface SolplateGeometry { + /** Length along 교축 (m) and width along 교각축 직각 (m). */ + L: number; + W: number; + /** Thickness at the 시점 end (T1) and the 종점 end (T2) (m). */ + T1: number; + T2: number; + /** Underside (EL m): the bearing top = block top (EL.X) + h3. */ + bottom: number; + /** Eight corners: [s, y, n] in the model's local frame (EL m). */ + corners: [number, number, number][]; + /** Volume (m³): L·W·(T1 + T2)/2. */ + volume: number; +} + +/** Solplate of the bearing at `block`, with the bearing height h3 (mm) and the 공통 규칙 parameters. */ +export function solplateGeometry(block: BlockModel, h3: number, p: SolplateParams): SolplateGeometry { + const L = (p.A + p.margin) / 1000; + const W = (p.B + p.margin) / 1000; + // Thickness change from the centre to each end: L·|S|/2, with S in % (mm). + const drop = ((p.A + p.margin) * Math.abs(p.slopePct)) / 200 / 1000; + const T = p.T / 1000; + const T1 = T + drop; + const T2 = T - drop; + const bottom = block.padTop + h3 / 1000; + const thickAt = (a: number) => T1 + ((T2 - T1) * (a + L / 2)) / L; + const corners: [number, number, number][] = []; + for (const a of [-L / 2, L / 2]) { + for (const c of [-W / 2, W / 2]) { + const [s, n] = blockFramePoint(block, a, c); + corners.push([s, bottom, n]); + corners.push([s, bottom + thickAt(a), n]); + } + } + return { L, W, T1, T2, bottom, corners, volume: (L * W * (T1 + T2)) / 2 }; +} diff --git a/tests/solplate.test.ts b/tests/solplate.test.ts new file mode 100644 index 0000000..9d85cbb --- /dev/null +++ b/tests/solplate.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; +import { buildPier } from '../src/core/geometry'; +import { solplateGeometry } from '../src/core/solplate'; +import { loadP1 } from './helpers'; + +const model = buildPier(loadP1()); +const front1 = model.blocks.filter((b) => b.row === '전열')[0]; +const params = { A: 700, B: 640, margin: 100, T: 30, slopePct: -1.91 }; + +describe('솔플레이트 기하 (1단계, 승인 2026-10-23)', () => { + it('L = A + 100 and W = B + 100 along the 교축 and the 교각축 직각', () => { + const g = solplateGeometry(front1, 181, params); + expect(g.L).toBeCloseTo(0.8, 6); + expect(g.W).toBeCloseTo(0.74, 6); + }); + + it('the thick end T1 is on the 시점 side (−s): T1 ≈ 38, T2 ≈ 22 mm, and T1 + T2 = 2T', () => { + const g = solplateGeometry(front1, 181, params); + expect(g.T1 * 1000).toBeCloseTo(37.64, 1); + expect(g.T2 * 1000).toBeCloseTo(22.36, 1); + expect((g.T1 + g.T2) * 1000).toBeCloseTo(60, 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('the underside is the bearing top: block top (EL.X) + h3', () => { + const g = solplateGeometry(front1, 181, params); + 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, 181, params); + 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, 181, params); + 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); + }); +});