feat(bearing): 일체 block as one stepped block per pair, with its rebar (승인 2026-10-26)

- integrated-block.ts: per pair (전열 k · 후열 k) one block centred on the
  pair midpoint, 2500 across the 교축 and 1200 along the 교각축, with a
  stepped top (each half to its own bearing row top). Only when 일체 is chosen
  and every pair is within 100 mm; otherwise 분리 is kept (B02).
- Rebar: S2 19 bars at 125 across the 2500 (10 front half, 9 back half), each
  leg from its half's top; S1 8 bars across the 1200, 3800 long, depth from
  the lower top. Totals per pier: S1 40, S2 95 (분리: 80 / 80).
- Scene and 3D rebar draw the stepped blocks instead of the per-bearing blocks.
- The per-bearing blocks stay for the solplate, the pads, and the body.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-26 12:02:33 +09:00
1 parent 2af5cfb144
commit 43a18182f3
7 files changed
+216 -6

No files matched your search

+83
View File
@@ -0,0 +1,83 @@
import { describe, expect, it } from 'vitest';
import { buildPier, type BlockModel } from '../src/core/geometry';
import { buildIntegratedBlocks, INTEGRATED_BLOCK } from '../src/core/integrated-block';
import { bearingBlockRebar, integratedBlockBars, totals } from '../src/core/rebar/bearing-block';
import { loadP1 } from './helpers';
const input = { ...loadP1(), bearingLayout: '일체' as const };
const model = buildPier(input);
describe('일체 블록 모델 (승인 2026-10-26)', () => {
it('P1 with 일체: one stepped block per pair (5), 2500 along the 교축 and 1200 along the 교각축', () => {
expect(model.integratedBlocks).toHaveLength(5);
expect(INTEGRATED_BLOCK).toEqual({ length: 2500, depth: 1200 });
const first = model.integratedBlocks[0];
expect(first.frontNo).toBe(1);
expect(first.backNo).toBe(6);
});
it('the block is centred on the pair midpoint, and its two halves keep their own tops (단이 있는 블록)', () => {
const ib = model.integratedBlocks[0];
const f = model.blocks.find((b) => b.no === 1)!;
const b = model.blocks.find((x) => x.no === 6)!;
expect(ib.n).toBeCloseTo((f.n + b.n) / 2, 9);
expect(ib.s).toBeCloseTo((f.s + b.s) / 2, 9);
expect(ib.topFront).toBe(f.top);
expect(ib.topBack).toBe(b.top);
// the front and back tops differ on P1 (150 vs 200 mm), so the block is stepped
expect(Math.abs(ib.topFront - ib.topBack)).toBeGreaterThan(0.01);
});
it('the 2500 block covers the pair: the front and back bearings sit 600 mm inside each end (C1 600)', () => {
const ib = model.integratedBlocks[0];
const f = model.blocks.find((b) => b.no === 1)!;
const b = model.blocks.find((x) => x.no === 6)!;
const half = INTEGRATED_BLOCK.length / 2000;
expect(ib.n - half).toBeCloseTo(f.n - 0.6, 2);
expect(ib.n + half).toBeCloseTo(b.n + 0.6, 2);
});
it('분리 keeps no integrated blocks', () => {
expect(buildPier(loadP1()).integratedBlocks).toEqual([]);
});
it('a staggered pair blocks 일체: no integrated blocks, the model stays 분리', () => {
const blocks = [{ no: 1, row: '전열', s: 1, n: -0.7, top: 0.5, bottom: 0, rowSkewDeg: 0 }, { no: 1, row: '후열', s: 1.2, n: 0.6, top: 0.5, bottom: 0, rowSkewDeg: 0 }] as unknown as BlockModel[];
expect(buildIntegratedBlocks(blocks, true)).toEqual([]);
});
});
describe('일체 블록 철근 (승인 2026-10-26, 받침블럭철근.md 일체 a 2500)', () => {
it('S2: 19 bars across the 2500 length, 10 on the front half and 9 on the back half (the centre bar on the front)', () => {
const rows = integratedBlockBars(model.integratedBlocks[0]).filter((r) => r.name === 'S2');
expect(rows.reduce((s, r) => s + r.count, 0)).toBe(19);
expect(rows.map((r) => r.count)).toEqual([10, 9]);
expect(rows[0].length).toBe(2500);
});
it('S1: 8 bars across the 1200 depth, 3800 long (2500 − 100 + 2 × 700)', () => {
const s1 = integratedBlockBars(model.integratedBlocks[0]).find((r) => r.name === 'S1')!;
expect(s1.count).toBe(8);
expect(s1.length).toBe(3800);
});
it('a pier of five integrated blocks has S1 40 and S2 95 (per-bearing 분리 gives 80 and 80)', () => {
const t = totals(bearingBlockRebar(model));
expect(t).toEqual({ S1: 40, S2: 95 });
const sep = totals(bearingBlockRebar(buildPier(loadP1())));
expect(sep).toEqual({ S1: 80, S2: 80 });
});
});
describe('일체 블록 3D 철근과 장면', () => {
it('the 3D rebar and the scene build for a 일체 pier, with the S2 bars counted per pair (19 each)', async () => {
const { buildRebar3D } = await import('../src/view/rebar3d');
const { buildPierScene } = await import('../src/view/scene');
const rb = buildRebar3D(model);
expect(rb.group.children.length).toBeGreaterThan(0);
const scene = buildPierScene(model, undefined);
// the blocks group holds the stepped halves (2 per pair) and no per-bearing blocks
expect(scene.parts.blocks.children.length).toBeGreaterThan(0);
expect(scene.parts.blocks.children.length).toBe(model.integratedBlocks.length * 2);
});
});