feat(core): lengths for coping, column and the B2 split (추천안)

- Coping: C9/C10 lower U = 1740 + 2h with h linear along the slope and 2300 on
  the Level (3522 / 6098 / 6249 / 6340 from the document); C12 (3717–10183),
  C13 11746, C14 3447.5, C15 3852, C16 4070.
- Column: P3 = C_t + 1.3·l_d(H19) lap (추천안 for Q-C6, lap 1146.6), P5 = C_t.
- Footing: B2 13646 is split into two pieces at a splice with 1.3·l_d(S25,
  SD500) (추천안 for Q-B6), piece ≈ 7776 each.
- Not done: C9/C11 cap (삿갓) lengths (Q-K5 open), P1/P2 per-line pieces.
- Tests against the document values; P1 summary updated.
- Not verified in a browser.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-22 11:37:08 +09:00
1 parent 73bff19aba
commit 5641bbe265
6 files changed
+105 -24

No files matched your search

+9 -1
View File
@@ -1,4 +1,4 @@
import { developmentLength, hookLength, NOMINAL_DIAMETER } from './common';
import { developmentLength, hookLength, NOMINAL_DIAMETER, spliceLength } from './common';
/**
* 기둥 철근 C4 (plan C4). Source: RAG/기둥철근 (1).md, 2026-10-22 최종 정리. Type-1 원형 중실 기둥.
@@ -93,7 +93,15 @@ export function columnRebar(x: ColumnInput = COLUMN_P1) {
const p6Hook = hookLength('tie90', dH16);
const p6Length = p6Straight + 2 * p6Hook;
// P3 lap: Q-C6 is open; recommended = 1.3·l_d of H19 (fck 27, fy 400).
const lapP3 = spliceLength(dH19, 400, 27);
const p3Length = 2 * Math.PI * tieRadius + lapP3;
const p5Length = 2 * Math.PI * tieRadius;
return {
p3Length: round(p3Length, 1),
p5Length: round(p5Length, 1),
lapP3: round(lapP3, 1),
r1: round(r1),
r2: round(r2),
angleStepDeg: 360 / x.nDiv,
+10
View File
@@ -62,3 +62,13 @@ export function hookLength(kind: HookKind, db: number): number {
}
return 2.5 * db * ((3 * Math.PI) / 4) + 6 * db;
}
/**
* Splice of a long bar into two pieces (추천안 for Q-B6): each piece is half the total plus half the lap,
* and the lap is l_s = 1.3·l_d. Used only when the bar is longer than the standard bar length.
*/
export function splitPieces(total: number, db: number, fy: number, fck: number, standard: number): { pieces: number; pieceLength: number; lap: number } | null {
if (total <= standard) return null;
const lap = spliceLength(db, fy, fck);
return { pieces: 2, pieceLength: total / 2 + lap / 2, lap };
}
+24
View File
@@ -71,3 +71,27 @@ export function c14Total(): number {
/** C15 (거꾸로 된 U, 가장 높은 계단) and C16 (L형, 2구간) counts (코핑철근.md §7). */
export const STEP_BAR_COUNTS = { C15: 11, C16: 22 } as const;
/**
* C9·C10 lower U length (코핑철근.md §4.3): 1740 + 2h. h rises linearly along the slope between the document's
* ends (x 300 → h 890.9, x 4550 → h 2178.8) and stays at the Level height 2300 from the Level edge (4950) on.
* The bar's upper cap (삿갓) length depends on l_cap, which is still open (Q-K5), so only the U is given here.
*/
export const SHEAR_U = { x0: 300, h0: 890.9, x1: 4550, h1: 2178.8, levelH: 2300, levelEdgeFromCentre: 1050, flat: 1740 } as const;
export function shearUHeight(x: number, coping: number): number {
const xs = Math.min(x, coping - x); // mirror to the left half
const slope = (SHEAR_U.h1 - SHEAR_U.h0) / (SHEAR_U.x1 - SHEAR_U.x0);
const h = SHEAR_U.h0 + (xs - SHEAR_U.x0) * slope;
return Math.min(h, SHEAR_U.levelH);
}
export function shearULength(x: number, coping: number): number {
return SHEAR_U.flat + 2 * shearUHeight(x, coping);
}
/** C12 straight lengths per row (코핑철근.md §6 table, mm) and C13. */
export const SIDE_LENGTHS = { C12: [3717, 5333, 6950, 8567, 10183], C13: 11746 } as const;
/** C15 (거꾸로 된 U, 3852) and C16 (L형, 4070 / 4069) lengths, mm (코핑철근.md §7). */
export const STEP_LENGTHS = { C15: 3852, C16: [4070, 4069] as const, C14: 3447.5 } as const;
+27 -22
View File
@@ -1,9 +1,10 @@
import type { PierModel } from '../geometry';
import { bearingBlockRebar, totals } from './bearing-block';
import { bottomPiece, topBarLength, widthLayout } from './coping-main';
import { shearBars, sideRows, C14_COUNTS, c14Total, STEP_BAR_COUNTS } from './coping';
import { shearBars, sideRows, C14_COUNTS, c14Total, STEP_BAR_COUNTS, shearULength, SIDE_LENGTHS, STEP_LENGTHS } from './coping';
import { columnRebar, COLUMN_P1 } from './column';
import { bottomBars, crossBars, sideBars, assemblyBars, shearB9 } from './footing';
import { splitPieces } from './common';
/** One line of a 철근표. length is null where the document gives no length yet. */
export interface RebarRow {
@@ -49,23 +50,27 @@ export function rebarSummary(model: PierModel): { rows: RebarRow[]; warnings: Re
rows.push({ member: 'coping', name: 'C7', size: 'H29', count: perRow, length: bottom.piece, note: '하면, 2조각 겹침이음' });
rows.push({ member: 'coping', name: 'C8', size: 'H29', count: perRow, length: bottom.piece, note: '하면, 2조각 겹침이음' });
const shear = shearBars(L);
rows.push({ member: 'coping', name: 'C9', size: 'H25', count: shear.c9.length, length: null, note: '전단 좌 @250' });
rows.push({ member: 'coping', name: 'C10', size: 'H25', count: shear.c10.length, length: null, note: '전단 Level @300' });
rows.push({ member: 'coping', name: 'C11', size: 'H25', count: shear.c11.length, length: null, note: '전단 우 @250' });
const uRange = (xs: number[]) => {
const u = xs.map((x) => Math.round(shearULength(x, L)));
return `${Math.min(...u)}–${Math.max(...u)}`;
};
rows.push({ member: 'coping', name: 'C9', size: 'H25', count: shear.c9.length, length: null, note: `전단 좌 @250, 하부 U ${uRange(shear.c9)} (삿갓 l_cap 미정)` });
rows.push({ member: 'coping', name: 'C10', size: 'H25', count: shear.c10.length, length: null, note: `전단 Level @300, 하부 U ${uRange(shear.c10)} (삿갓 l_cap 미정)` });
rows.push({ member: 'coping', name: 'C11', size: 'H25', count: shear.c11.length, length: null, note: `전단 우 @250, 하부 U ${uRange(shear.c11)} (삿갓 l_cap 미정)` });
const side = sideRows(41.288, 42.538);
rows.push({ member: 'coping', name: 'C12', size: 'H25', count: side.c12.length, length: null, note: '측면, 경사 하면에서 끊김' });
rows.push({ member: 'coping', name: 'C13', size: 'H25', count: side.c13.length, length: null, note: '측면, 끝면까지' });
rows.push({ member: 'coping', name: 'C14', size: 'H19', count: c14Total(), length: null, note: `직각 수평 ${C14_COUNTS.upperLines * C14_COUNTS.upperPerLine}+${C14_COUNTS.lower.join('+')}` });
rows.push({ member: 'coping', name: 'C15', size: 'H19', count: STEP_BAR_COUNTS.C15, length: null, note: '계단부, 가장 높은 구간' });
rows.push({ member: 'coping', name: 'C16', size: 'H19', count: STEP_BAR_COUNTS.C16, length: null, note: '계단부, 3·4구간' });
rows.push({ member: 'coping', name: 'C12', size: 'H25', count: side.c12.length, length: null, note: `측면, 경사 하면에서 끊김, 일자 ${SIDE_LENGTHS.C12[0]}–${SIDE_LENGTHS.C12[4]}` });
rows.push({ member: 'coping', name: 'C13', size: 'H25', count: side.c13.length, length: SIDE_LENGTHS.C13, note: '측면, 끝면까지' });
rows.push({ member: 'coping', name: 'C14', size: 'H19', count: c14Total(), length: STEP_LENGTHS.C14, note: `직각 수평 ${C14_COUNTS.upperLines * C14_COUNTS.upperPerLine}+${C14_COUNTS.lower.join('+')}` });
rows.push({ member: 'coping', name: 'C15', size: 'H19', count: STEP_BAR_COUNTS.C15, length: STEP_LENGTHS.C15, note: '계단부, 가장 높은 구간 (거꾸로 U)' });
rows.push({ member: 'coping', name: 'C16', size: 'H19', count: STEP_BAR_COUNTS.C16, length: STEP_LENGTHS.C16[0], note: `계단부, 3·4구간 (${STEP_LENGTHS.C16[0]} / ${STEP_LENGTHS.C16[1]})` });
// Column (기둥철근.md · plan C4), P1 defaults
const col = columnRebar(COLUMN_P1);
rows.push({ member: 'column', name: 'P1', size: 'S32', count: col.mainP1, length: null, note: '외측 r 875, 커플러로 이음' });
rows.push({ member: 'column', name: 'P2', size: 'S32', count: col.mainP2, length: null, note: '내측 r 843.2, 커플러로 이음' });
rows.push({ member: 'column', name: '커플러', size: 'L150 Ø60', count: col.couplers, length: 150, note: '@3000, P2 +150' });
rows.push({ member: 'column', name: 'P3', size: 'H19', count: col.p3Count, length: null, note: '일반 구간 띠철근 @300' });
rows.push({ member: 'column', name: 'P5', size: 'H19', count: col.p5Count, length: null, note: '단부 띠철근 @120' });
rows.push({ member: 'column', name: 'P3', size: 'H19', count: col.p3Count, length: col.p3Length, note: `일반 구간 띠철근 @300, 겹침 ${col.lapP3} (추천안, Q-C6)` });
rows.push({ member: 'column', name: 'P5', size: 'H19', count: col.p5Count, length: col.p5Length, note: '단부 띠철근 @120, 커플러 1개' });
rows.push({ member: 'column', name: 'P4', size: 'H19', count: col.p4Count, length: col.p4SemicircleLength * 2, note: '확대기초 안, 반원 2개 (길이는 반원 2개 합) · B11 간섭 무시' });
rows.push({ member: 'column', name: 'P6', size: 'H16', count: col.p6Count, length: col.p6Length, note: '크로스타이, 8단 × 4' });
@@ -75,18 +80,18 @@ export function rebarSummary(model: PierModel): { rows: RebarRow[]; warnings: Re
const Lf = f.length * 1000;
const Wf = f.width * 1000;
const Hf = (f.top - f.bottom) * 1000;
for (const b of bottomBars(Lf, Wf, Hf)) {
rows.push({ member: 'footing', name: b.name, size: b.size, count: b.count, length: b.length, note: '교각축' });
if (b.length > STANDARD_BAR) {
warnings.push({ code: 'RB-F08', message: `${b.name} 길이 ${Math.round(b.length)} mm가 정척 ${STANDARD_BAR} mm를 넘어 이음이 필요합니다 (Q-B6).` });
// Bars longer than the standard length are split in two (추천안, Q-B6). The count is still the bar count.
const addBar = (b: { name: string; size: string; count: number; length: number }, note: string) => {
const split = splitPieces(b.length, 25.4, 500, 27, STANDARD_BAR);
if (split) {
rows.push({ member: 'footing', name: b.name, size: b.size, count: b.count, length: split.pieceLength, note: `${note}, 2조각 이음 (추천안, 겹침 ${Math.round(split.lap)})` });
warnings.push({ code: 'RB-F08', message: `${b.name} 길이 ${Math.round(b.length)} mm > 정척 ${STANDARD_BAR} mm: 2조각 이음을 추천안으로 적용 (Q-B6).` });
} else {
rows.push({ member: 'footing', name: b.name, size: b.size, count: b.count, length: b.length, note });
}
}
for (const b of crossBars(Lf, Wf, Hf)) {
rows.push({ member: 'footing', name: b.name, size: b.size, count: b.count, length: b.length, note: '교각축 직각' });
if (b.length > STANDARD_BAR) {
warnings.push({ code: 'RB-F08', message: `${b.name} 길이 ${Math.round(b.length)} mm가 정척 ${STANDARD_BAR} mm를 넘어 이음이 필요합니다 (Q-B6).` });
}
}
};
for (const b of bottomBars(Lf, Wf, Hf)) addBar(b, '교각축');
for (const b of crossBars(Lf, Wf, Hf)) addBar(b, '교각축 직각');
for (const b of sideBars(Lf, Wf)) rows.push({ member: 'footing', name: b.name, size: b.size, count: b.count, length: b.length, note: '측면 수평' });
for (const b of assemblyBars(Lf, Wf)) rows.push({ member: 'footing', name: b.name, size: b.size, count: b.count, length: b.length, note: '조립 수평' });
const b9 = shearB9(Lf, Wf, Hf);