feat(rebar): block rebar settings as input (S1·S2 spacing separate, S1 depth from the cover and bar diameters)

- PierInput.blockRebar (partial): cover top, side cover, first gap, spacing S1
  and S2 (separate), leg length. Defaults are the P1 values, so P1 is unchanged.
- S2 depth = top cover; S1 depth = top cover + S1/2 + S2/2 (H19: 69.1 at the default).
- The model carries the settings; the bearing-block rebar, the 3D bars, and the
  summary use them.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-26 14:08:58 +09:00
1 parent c4d4957dd2
commit e1497176ea
6 files changed
+141 -74

No files matched your search

+4
View File
@@ -1,5 +1,6 @@
import { buildAxis, copingSpan, fromAxis, kinkArc, nearKink, toAxis } from './axis';
import { buildIntegratedBlocks, type IntegratedBlock } from './integrated-block';
import { type BlockRebarSettings, DEFAULT_BLOCK_REBAR } from './rebar/bearing-block';
import { layoutText, resolveRowCounts, rowLayout, type PileGeometry } from './piles';
import type { PierInput, Row } from './types';
import { type CopingGeom, P1_COPING_GEOM } from './coping-geom';
@@ -173,6 +174,8 @@ export interface PierModel {
blocks: BlockModel[];
/** 일체 블록 (승인 2026-10-26): empty unless 일체 is chosen and allowed. The per-bearing blocks stay for the solplate and pads. */
integratedBlocks: IntegratedBlock[];
/** 받침블럭 철근 설정 used by the rebar (승인 2026-10-26). */
blockRebar: BlockRebarSettings;
columns: ColumnModel[];
footings: FootingModel[];
blindings: BlindingModel[];
@@ -460,6 +463,7 @@ export function buildPier(input: PierInput): PierModel {
bearings: [...frontPlaced, ...backPlaced].map((b) => ({ no: b.no, row: b.row, k: b.k, s: b.s, g: b.g, n: b.n, z: b.z })),
blocks,
integratedBlocks: buildIntegratedBlocks(blocks, input.bearingLayout === '일체'),
blockRebar: { ...DEFAULT_BLOCK_REBAR, ...input.blockRebar },
columns,
footings,
blindings,
+82 -54
View File
@@ -3,19 +3,42 @@ import { INTEGRATED_BLOCK, type IntegratedBlock } from '../integrated-block';
/**
* 받침블럭 철근 (spec §6.3 · plan C2), 배치 분리: one block per bearing, 1200 × 1200.
* S2 is the upper bar (cover 50 from the block top); S1 is the lower bar (69.1 below the top).
* Both have the same layout: 7@125 = 875 with ends of 162.5 in a 1200 mm block, and legs of 700 below the bar.
* The settings come from the input (승인 2026-10-26): S1 and S2 have their own spacing (따로 입력), the side cover c_s
* and the first-bar gap e_s are shared, and the S1 depth is the top cover + half of S1 + half of S2 (피복 + S1/2 + S2/2).
* S2 is the upper bar (depth = top cover); S1 is the lower bar.
*/
export const BLOCK_REBAR = {
dim: 1200,
cover: 50,
firstBarGap: 75,
spacing: 125,
export interface BlockRebarSettings {
/** 상면 피복 (철근 중심까지, mm). */
coverTop: number;
/** 측면 피복 (S1 다리·S2 끝, mm). */
coverSide: number;
/** 첫 철근 최소 여유 (측면 피복 다음, mm). */
firstGap: number;
/** 간격 S1 (mm). */
spacingS1: number;
/** 간격 S2 (mm). */
spacingS2: number;
/** 다리 길이 (철근 중심에서 아래로, mm). */
legLength: number;
}
/** Defaults: the P1 values of 받침블럭철근.md (피복 50 · 여유 75 · @125 · 다리 700). */
export const DEFAULT_BLOCK_REBAR: BlockRebarSettings = {
coverTop: 50,
coverSide: 50,
firstGap: 75,
spacingS1: 125,
spacingS2: 125,
legLength: 700,
/** Depth of each bar centre below the block top (mm). */
depthS2: 50,
depthS1: 69.1,
} as const;
};
/** H19 bar diameter (mm) of both S1 and S2. */
const H19 = 19.1;
/** Depth of each bar centre below the block top (mm): S2 = top cover; S1 = top cover + S1/2 + S2/2. */
export function barDepths(s: BlockRebarSettings): { S1: number; S2: number } {
return { S2: s.coverTop, S1: s.coverTop + H19 / 2 + H19 / 2 };
}
export interface BarRow {
name: 'S1' | 'S2';
@@ -26,31 +49,35 @@ export interface BarRow {
firstEnd: number;
/** Straight length plus two legs (mm). */
length: number;
/** Coping embedment of the legs (mm): 700 − (block height − bar depth). */
/** Coping embedment of the legs (mm): legLength − (block height − bar depth). */
embedment: number;
}
/** Bars along one 1200 mm side: available = dim − 2·(cover + first gap); gaps = floor(available / spacing). */
export function barsAcross(dim: number = BLOCK_REBAR.dim): { count: number; spacing: number; firstEnd: number } {
const available = dim - 2 * (BLOCK_REBAR.cover + BLOCK_REBAR.firstBarGap);
const gaps = Math.floor(available / BLOCK_REBAR.spacing);
const span = gaps * BLOCK_REBAR.spacing;
/** Bars along one side: available = dim − 2·(cover + first gap); gaps = floor(available / spacing). */
export function barsAcross(
dim: number = 1200,
spacing: number = DEFAULT_BLOCK_REBAR.spacingS2,
s: BlockRebarSettings = DEFAULT_BLOCK_REBAR,
): { count: number; spacing: number; firstEnd: number } {
const available = dim - 2 * (s.coverSide + s.firstGap);
const gaps = Math.floor(available / spacing);
const span = gaps * spacing;
const leftover = available - span;
return { count: gaps + 1, spacing: BLOCK_REBAR.spacing, firstEnd: BLOCK_REBAR.cover + BLOCK_REBAR.firstBarGap + leftover / 2 };
return { count: gaps + 1, spacing, firstEnd: s.coverSide + s.firstGap + leftover / 2 };
}
/** Length of one bar (mm): the straight part a − 2·c_s plus two legs of 700 (spec §5; 2500 for the 1200 block). */
export function barLength(dim: number = BLOCK_REBAR.dim): number {
return dim - 2 * BLOCK_REBAR.cover + 2 * BLOCK_REBAR.legLength;
/** Length of one bar (mm): the straight part a − 2·c_s plus two legs (2500 for the 1200 block with the defaults). */
export function barLength(dim: number = 1200, s: BlockRebarSettings = DEFAULT_BLOCK_REBAR): number {
return dim - 2 * s.coverSide + 2 * s.legLength;
}
/** Rebar of one block; blockHeight is the bearing block height in mm (h1 front or h2 back, pad included). */
export function blockBars(blockHeight: number, dim: number = BLOCK_REBAR.dim): BarRow[] {
const across = barsAcross(dim);
const length = barLength(dim);
/** Rebar of one block; blockHeight is the bearing block height in mm (block top − bottom, pad excluded). */
export function blockBars(blockHeight: number, dim: number = 1200, s: BlockRebarSettings = DEFAULT_BLOCK_REBAR): BarRow[] {
const depth = barDepths(s);
const length = barLength(dim, s);
return [
{ name: 'S1', ...across, length, embedment: BLOCK_REBAR.legLength - (blockHeight - BLOCK_REBAR.depthS1) },
{ name: 'S2', ...across, length, embedment: BLOCK_REBAR.legLength - (blockHeight - BLOCK_REBAR.depthS2) },
{ name: 'S1', ...barsAcross(dim, s.spacingS1, s), length, embedment: s.legLength - (blockHeight - depth.S1) },
{ name: 'S2', ...barsAcross(dim, s.spacingS2, s), length, embedment: s.legLength - (blockHeight - depth.S2) },
];
}
@@ -61,45 +88,46 @@ export interface BlockRebar {
}
/**
* Rebar of every block of a pier. The block height h_b is the model's own block (coping top → block top, pad excluded),
* since the bar depths are measured from the block top. h1 / h2 include the 50 pad and would count it twice.
* Rebar of one 일체 block (승인 2026-10-26, 받침블럭철근.md "일체 a 2500"): S2 runs along the 교각축 (1200) with the bars
* across the 2500 length (10 on the front half, 9 on the back half); its legs are measured from the top of the half it sits in.
* S1 runs along the 2500 length, across the 1200 depth; its depth is taken from the lower top.
*/
export function bearingBlockRebar(model: PierModel): BlockRebar[] {
// 일체 (승인 2026-10-26): one block per pair replaces the two per-bearing blocks.
if (model.integratedBlocks.length > 0) {
return model.integratedBlocks.map((ib) => ({ no: ib.frontNo, row: '전열', bars: integratedBlockBars(ib) }));
}
return model.blocks.map((b) => ({
no: b.no,
row: b.row,
bars: blockBars((b.top - b.bottom) * 1000),
}));
}
/**
* Rebar of one 일체 block (승인 2026-10-26, 받침블럭철근.md "일체 a 2500").
* S2 runs along the 교각축 (1200), so its bars are spaced across the 2500 length: 19 bars at 125. Its legs are measured
* from the top of the half it sits in; the bar at the step line (centre) counts with the front half.
* S1 runs along the 2500 length, 8 bars across the 1200 depth, its depth taken from the lower top (cover on the higher half is larger).
*/
export function integratedBlockBars(ib: IntegratedBlock): BarRow[] {
export function integratedBlockBars(ib: IntegratedBlock, s: BlockRebarSettings = DEFAULT_BLOCK_REBAR): BarRow[] {
const half = INTEGRATED_BLOCK.length / 2;
const s2 = barsAcross(INTEGRATED_BLOCK.length);
const depth = barDepths(s);
const s2 = barsAcross(INTEGRATED_BLOCK.length, s.spacingS2, s);
const frontCount = Array.from({ length: s2.count }, (_, k) => s2.firstEnd + k * s2.spacing).filter((x) => x <= half + 1e-9).length;
const backCount = s2.count - frontCount;
const hFront = (ib.topFront - ib.bottom) * 1000;
const hBack = (ib.topBack - ib.bottom) * 1000;
const legS2 = (h: number) => BLOCK_REBAR.legLength - (h - BLOCK_REBAR.depthS2);
const s1 = barsAcross(INTEGRATED_BLOCK.depth);
const legS2 = (h: number) => s.legLength - (h - depth.S2);
const s1 = barsAcross(INTEGRATED_BLOCK.depth, s.spacingS1, s);
const hLow = Math.min(hFront, hBack);
const rows: BarRow[] = [
{ name: 'S2', count: frontCount, spacing: s2.spacing, firstEnd: s2.firstEnd, length: barLength(INTEGRATED_BLOCK.depth), embedment: legS2(hFront) },
{ name: 'S2', count: frontCount, spacing: s2.spacing, firstEnd: s2.firstEnd, length: barLength(INTEGRATED_BLOCK.depth, s), embedment: legS2(hFront) },
];
if (backCount > 0) rows.push({ name: 'S2', count: backCount, spacing: s2.spacing, firstEnd: s2.firstEnd, length: barLength(INTEGRATED_BLOCK.depth), embedment: legS2(hBack) });
rows.push({ name: 'S1', count: s1.count, spacing: s1.spacing, firstEnd: s1.firstEnd, length: barLength(INTEGRATED_BLOCK.length), embedment: BLOCK_REBAR.legLength - (hLow - BLOCK_REBAR.depthS1) });
if (backCount > 0) rows.push({ name: 'S2', count: backCount, spacing: s2.spacing, firstEnd: s2.firstEnd, length: barLength(INTEGRATED_BLOCK.depth, s), embedment: legS2(hBack) });
rows.push({ name: 'S1', count: s1.count, spacing: s1.spacing, firstEnd: s1.firstEnd, length: barLength(INTEGRATED_BLOCK.length, s), embedment: s.legLength - (hLow - depth.S1) });
return rows;
}
/**
* Rebar of every block of a pier. The block height is the model's own block (coping top → block top, pad excluded),
* since the bar depths are measured from the block top. The settings come from the model (input).
*/
export function bearingBlockRebar(model: PierModel): BlockRebar[] {
const s = model.blockRebar;
// 일체 (승인 2026-10-26): one block per pair replaces the two per-bearing blocks.
if (model.integratedBlocks.length > 0) {
return model.integratedBlocks.map((ib) => ({ no: ib.frontNo, row: '전열', bars: integratedBlockBars(ib, s) }));
}
return model.blocks.map((b) => ({
no: b.no,
row: b.row,
bars: blockBars((b.top - b.bottom) * 1000, 1200, s),
}));
}
/** Total bars of one pier: bars per block × blocks, for S1 and S2 separately. */
export function totals(rebar: BlockRebar[]): { S1: number; S2: number } {
return rebar.reduce(
+2 -2
View File
@@ -148,8 +148,8 @@ export function rebarSummary(
if (shortest < anchorage) {
warnings.push({ code: 'RB-S04', message: `받침블럭 철근 묻힘 ${Math.round(shortest)} mm < 정착길이 ${anchorage} mm (코핑 안 묻힘 부족)`, target: 'bearing' });
}
rows.push({ member: 'bearing', name: 'S1', size: 'H19', count: blk.S1, length: barLength(), note: `블럭 배치 분리, 블럭당 8본 · 묻힘 ${range(emb1)} (블럭 상면 − 69.1)` });
rows.push({ member: 'bearing', name: 'S2', size: 'H19', count: blk.S2, length: barLength(), note: `블럭 배치 분리, 블럭당 8본 · 묻힘 ${range(emb2)} (블럭 상면 − 50)` });
rows.push({ member: 'bearing', name: 'S1', size: 'H19', count: blk.S1, length: barLength(1200, model.blockRebar), note: `블럭 배치 분리, 블럭당 8본 · 묻힘 ${range(emb1)} (블럭 상면 − 69.1)` });
rows.push({ member: 'bearing', name: 'S2', size: 'H19', count: blk.S2, length: barLength(1200, model.blockRebar), note: `블럭 배치 분리, 블럭당 8본 · 묻힘 ${range(emb2)} (블럭 상면 − 50)` });
// Coping (코핑철근.md §5–§8)
const width = widthLayout(B);
+2
View File
@@ -70,6 +70,8 @@ export interface PierInput {
lowWaterEl?: number;
/** 받침 종류·용량 as text (승인 2026-10-26). Absent = 탄성받침 (the default). */
bearingType?: string;
/** 받침블럭 철근 설정 (승인 2026-10-26): 피복, 여유, 간격 S1·S2 (따로), 다리. Absent = the P1 defaults. */
blockRebar?: Partial<{ coverTop: number; coverSide: number; firstGap: number; spacingS1: number; spacingS2: number; legLength: number }>;
/** 받침 배치 (승인 2026-10-26): 분리 (default) or 일체. 일체 is allowed only when every front–back pair is within 100 mm. */
bearingLayout?: '분리' | '일체';
strataRows?: StrataRow[];