feat(rebar): add downward 90° hooks (135 mm) to P6 crossties in 3D rebar model
This commit is contained in:
1 parent
4aa8dbb3cb
commit
a2317caf6c
15 files changed
+740
-133
No files matched your search
@@ -10,8 +10,6 @@ export const BLOCK_REBAR = {
|
||||
cover: 50,
|
||||
firstBarGap: 75,
|
||||
spacing: 125,
|
||||
/** Straight part of the bar for the 1200 block (spec table: 1100 + 700 × 2). */
|
||||
straight: 1100,
|
||||
legLength: 700,
|
||||
/** Depth of each bar centre below the block top (mm). */
|
||||
depthS2: 50,
|
||||
@@ -40,10 +38,15 @@ export function barsAcross(dim: number = BLOCK_REBAR.dim): { count: number; spac
|
||||
return { count: gaps + 1, spacing: BLOCK_REBAR.spacing, firstEnd: BLOCK_REBAR.cover + BLOCK_REBAR.firstBarGap + 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;
|
||||
}
|
||||
|
||||
/** 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 = BLOCK_REBAR.straight + 2 * BLOCK_REBAR.legLength;
|
||||
const length = barLength(dim);
|
||||
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) },
|
||||
@@ -56,12 +59,15 @@ export interface BlockRebar {
|
||||
bars: BarRow[];
|
||||
}
|
||||
|
||||
/** Rebar of every block of a pier, using the model's block heights (h1 front, h2 back; pad included, Q16 결정). */
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export function bearingBlockRebar(model: PierModel): BlockRebar[] {
|
||||
return model.blocks.map((b) => ({
|
||||
no: b.no,
|
||||
row: b.row,
|
||||
bars: blockBars(b.row === '전열' ? model.h1 * 1000 : model.h2 * 1000),
|
||||
bars: blockBars((b.top - b.bottom) * 1000),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,15 @@ export function columnRebar(x: ColumnInput = COLUMN_P1, m: Material = DEFAULT_MA
|
||||
const zTop1 = x.Hc + x.hcop - x.eP1;
|
||||
const lD2 = developmentLength(dS32, m.fyS, m.fck);
|
||||
const zTop2 = x.Hc + lD2;
|
||||
// Bottom of P1 and P2: 확대기초 하면 2단 (B7) 위 + (d_B7 + d_P1)/2 (기둥철근.md §3.2, the same for both).
|
||||
const zBot = -x.Hf + layers(x.Hf).B7 + (NOMINAL_DIAMETER.S19 + dS32) / 2;
|
||||
const couplerLevels = Array.from({ length: Math.max(0, Math.floor(zTop1 / x.ctcCoupler) - 1) }, (_, i) => (i + 1) * x.ctcCoupler);
|
||||
// Pieces of each main bar between its couplers (기둥철근.md §3.2): the bottom piece carries the 90° hook (16.5·d = 525).
|
||||
const p1Hook = Math.round(hookLength('main90', dS32));
|
||||
const p1Points = [zBot, ...couplerLevels, zTop1];
|
||||
const p1Pieces = p1Points.slice(1).map((z, i) => round(z - p1Points[i] + (i === 0 ? p1Hook : 0)));
|
||||
const p2Points = [zBot, ...couplerLevels.map((z) => z + x.dzCp2), zTop2];
|
||||
const p2Pieces = p2Points.slice(1).map((z, i) => round(z - p2Points[i]));
|
||||
const couplersP1 = couplerLevels.length;
|
||||
const mainLines = x.nDiv;
|
||||
const mainP1 = mainLines * (couplersP1 + 1);
|
||||
@@ -126,7 +134,7 @@ export function columnRebar(x: ColumnInput = COLUMN_P1, m: Material = DEFAULT_MA
|
||||
for (let z = z4first; z <= x.Hc; z += x.ctcP6) p6Levels.push(z);
|
||||
const p6Straight = 2 * tieRadius;
|
||||
const p6Hook = hookLength('tie90', dH16);
|
||||
const p6Length = p6Straight + 2 * p6Hook;
|
||||
const p6Length = p6Straight + 2 * Math.round(p6Hook); // 135 mm per hook, as in the document
|
||||
|
||||
// P3 lap: Q-C6 is open; recommended = 1.3·l_d of H19 with the material inputs.
|
||||
const lapP3 = spliceLength(dH19, m.fyH, m.fck);
|
||||
@@ -142,6 +150,10 @@ export function columnRebar(x: ColumnInput = COLUMN_P1, m: Material = DEFAULT_MA
|
||||
angleStepDeg: 360 / x.nDiv,
|
||||
zTop1: round(zTop1),
|
||||
zTop2: round(zTop2),
|
||||
zBot: round(zBot, 2),
|
||||
p1Hook,
|
||||
p1Pieces,
|
||||
p2Pieces,
|
||||
couplerP1: couplerLevels,
|
||||
couplerP2: couplerLevels.map((z) => z + x.dzCp2),
|
||||
mainP1,
|
||||
|
||||
+148
-4
@@ -2,6 +2,7 @@
|
||||
* 코핑 철근 (plan C3). Source: RAG/코핑철근.md, 2026-10-22 최종 정리 (spec §6.4 is older and is not used here).
|
||||
* Positions are along the coping length from its start, in mm.
|
||||
*/
|
||||
import { DEFAULT_MATERIAL, developmentLength, hookLength, Material, NOMINAL_DIAMETER } from './common';
|
||||
export const COPING_REBAR = {
|
||||
/** 피복 (최외측 철근 중심까지). */
|
||||
cover: 100,
|
||||
@@ -15,6 +16,8 @@ export const COPING_REBAR = {
|
||||
spacingS: 250,
|
||||
/** C12·C13 측면 수평 간격. */
|
||||
spacingSide: 250,
|
||||
/** 테이퍼 높이 h2 (코핑철근.md §2). */
|
||||
taperHeight: 1500,
|
||||
/** C14 위 2줄 간격 / 아래 줄 수평 간격 / 아래 줄 연직 간격 / 끝 여유. */
|
||||
c14: { upperSpacing: 1000, lowerSpacing: 2000, verticalSpacing: 500, endGap: 500 },
|
||||
} as const;
|
||||
@@ -39,7 +42,7 @@ export interface ShearBars {
|
||||
*/
|
||||
export function shearBars(coping: number, xc = coping / 2): ShearBars {
|
||||
const { spacingL, spacingS, levelWidth } = COPING_REBAR;
|
||||
const kMax = Math.floor(levelWidth / 2 / spacingL) + 1;
|
||||
const kMax = Math.ceil(levelWidth / 2 / spacingL); // k_max = ceil((W_L/2)/s_L), spec §3.1
|
||||
const c10 = Array.from({ length: 2 * kMax + 1 }, (_, i) => Math.round(xc + (i - kMax) * spacingL));
|
||||
const left = c10[0];
|
||||
const right = c10[c10.length - 1];
|
||||
@@ -90,12 +93,153 @@ 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;
|
||||
/** C13 sits 127 in from each end face (코핑철근.md §6.2). */
|
||||
export const SIDE_C13_END = 127;
|
||||
|
||||
/** Leg of each ㄷ형 bar along the side, 1.3·l_d = 1525 (Q-K9). */
|
||||
export const SIDE_HOOK_LEG = 1525;
|
||||
|
||||
export interface SideBar {
|
||||
/** Row height (m, EL). */
|
||||
z: number;
|
||||
/** Distance from each end face to where the 일자 bar starts (mm). */
|
||||
xw: number;
|
||||
/** Length of the 일자 bar, L − 2·x_w (mm). */
|
||||
straight: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* C12 and C13 rows for a coping of length L (mm), T형 (코핑철근.md §6.2).
|
||||
* C12 meets the sloped bottom at x_w = c + (z_break − z_k)·(x_L0 − c)/h2, with x_L0 = L/2 − W_L/2.
|
||||
* C13 starts at x_w = 127. Each row has two 일자 bars (side faces) and two ㄷ bars (one at each end).
|
||||
*/
|
||||
export function copingSideBars(L: number, zBottom: number, zBreak: number): { c12: SideBar[]; c13: SideBar[] } {
|
||||
const c = COPING_REBAR.cover;
|
||||
const xL0 = L / 2 - COPING_REBAR.levelWidth / 2;
|
||||
const bar = (z: number, xw: number): SideBar => ({ z, xw, straight: L - 2 * xw });
|
||||
const rows = sideRows(zBottom, zBreak);
|
||||
return {
|
||||
c12: rows.c12.map((z) => bar(z, c + ((zBreak - z) * 1000 * (xL0 - c)) / COPING_REBAR.taperHeight)),
|
||||
c13: rows.c13.map((z) => bar(z, SIDE_C13_END)),
|
||||
};
|
||||
}
|
||||
|
||||
/** 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;
|
||||
|
||||
/**
|
||||
* Stair zone of the P1 coping (코핑철근.md §7.1): each segment from x to x (mm from the coping start), and its top rise
|
||||
* above the lowest top (mm). The stair positions are the document's; the model has no stair geometry yet.
|
||||
*/
|
||||
export const STAIR_SEGMENTS = [
|
||||
{ from: 2196, to: 4701, rise: 72 }, // 2구간
|
||||
{ from: 4701, to: 7207, rise: 144 }, // 3구간
|
||||
{ from: 7207, to: 9712, rise: 215 }, // 4구간
|
||||
{ from: 9712, to: 12000, rise: 287 }, // 5구간 (to = L in the document)
|
||||
] as const;
|
||||
|
||||
/** C14 end gap e_14 (코핑철근.md §8.1) and the inset of its ends from the side faces: c + (d_shear + d_14)/2 = 122.25. */
|
||||
const C14_END_GAP = 500;
|
||||
export const C14_INSET = COPING_REBAR.cover + (NOMINAL_DIAMETER.H25 + NOMINAL_DIAMETER.H19) / 2;
|
||||
|
||||
/** Development length l_d of H19 (882 for fy 400, fck 27), rounded to mm as the document does (§7.2). */
|
||||
export function stepDevLength(m: Material = DEFAULT_MATERIAL): number {
|
||||
return Math.round(developmentLength(NOMINAL_DIAMETER.H19, m.fyH, m.fck));
|
||||
}
|
||||
|
||||
export interface C14Bar {
|
||||
/** 1–2: upper lines (C2, C3). 3–6: lower rows from the 7th, 5th, 3rd and 1st side rows. */
|
||||
line: number;
|
||||
/** Position along the coping (mm from its start). */
|
||||
x: number;
|
||||
/** EL of the bar (m). */
|
||||
z: number;
|
||||
/** false when the 연직 발 would end below the coping underside at x, so both legs are left out (§8). */
|
||||
legs: boolean;
|
||||
}
|
||||
|
||||
/** Underside of the coping (EL m) at x (mm from the start), T형: sloped from each end to the Level zone, flat in the middle. */
|
||||
export function undersideAt(L: number, x: number, endBottom: number, levelBottom: number): number {
|
||||
const xL0 = L / 2 - COPING_REBAR.levelWidth / 2;
|
||||
return endBottom + (levelBottom - endBottom) * Math.min(Math.min(x, L - x) / xL0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* C14 ⊓ bars across the width (코핑철근.md §8.1). Upper lines hang from C2 and C3 at @1000 from the column centre.
|
||||
* Lower rows use the side rows 7, 5, 3, 1 from the bottom (k = m − 2(j − 1)): odd j at x_c ± 2000k, even j at x_c ± 1000 ± 2000k,
|
||||
* only where that side row exists (x_w ≤ x ≤ L − x_w). `sideRows` are the side rows (z m, x_w mm), ascending.
|
||||
*/
|
||||
export function c14Bars(L: number, topEl: number, sideRows: { z: number; xw: number }[], under: (x: number) => number): C14Bar[] {
|
||||
const leg = Math.round(hookLength('main90', NOMINAL_DIAMETER.H19)) / 1000; // 296 mm, EL m
|
||||
const xc = L / 2;
|
||||
const lo = COPING_REBAR.cover + C14_END_GAP;
|
||||
const hi = L - lo;
|
||||
const out: C14Bar[] = [];
|
||||
const dUpper = (NOMINAL_DIAMETER.H29 + NOMINAL_DIAMETER.H19) / 2000; // m, over C2 / C3 (H29)
|
||||
for (const k of [2, 3]) {
|
||||
const z = topEl - 0.127 - (k - 1) * 0.057 + dUpper;
|
||||
for (let q = -10; q <= 10; q++) {
|
||||
const x = xc + q * 1000;
|
||||
if (x >= lo && x <= hi) out.push({ line: k - 1, x, z, legs: z - leg >= under(x) });
|
||||
}
|
||||
}
|
||||
const dLower = (NOMINAL_DIAMETER.H25 + NOMINAL_DIAMETER.H19) / 2000; // m, over a H25 side row
|
||||
for (let j = 1; 7 - 2 * (j - 1) >= 1; j++) {
|
||||
const row = sideRows[7 - 2 * (j - 1) - 1];
|
||||
const a = Math.max(lo, row.xw);
|
||||
const b = Math.min(hi, L - row.xw);
|
||||
for (let q = -10; q <= 10; q++) {
|
||||
const x = xc + (j % 2 === 1 ? 0 : 1000) + 2000 * q;
|
||||
if (x >= a && x <= b) out.push({ line: 2 + j, x, z: row.z + dLower, legs: row.z + dLower - leg >= under(x) });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** C14 length (mm): the straight run between the inset ends plus two 15.5·d_b legs (§8.2). 3447.5 in the document. */
|
||||
export function c14Length(B: number): number {
|
||||
return B - 2 * C14_INSET + 2 * Math.round(hookLength('main90', NOMINAL_DIAMETER.H19)); // legs rounded to mm (296)
|
||||
}
|
||||
|
||||
export interface StepBar {
|
||||
/** Position across the width, mm from the centre line. */
|
||||
n: number;
|
||||
/** Start and end along the coping (mm). */
|
||||
x0: number;
|
||||
x1: number;
|
||||
/** EL of the horizontal run (m). */
|
||||
z: number;
|
||||
/** Legs: C15 hangs at both ends, C16 only at its start. */
|
||||
legs: 'both' | 'start';
|
||||
/** Leg length l_d (mm). */
|
||||
leg: number;
|
||||
/** Developed length (mm): the horizontal run plus the leg(s) at its ends. */
|
||||
length: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* C15 (거꾸로 된 U, on the highest segment) and C16 (L형, on segments 3 and 4), 11 bars each across the width (코핑철근.md §7).
|
||||
* Across: 137 from the cover line, then @250 (피복선 기준). Along: from each segment start + c to the next end − c.
|
||||
*/
|
||||
export function stairBars(L: number, B: number, topEl: number, m: Material = DEFAULT_MATERIAL): { c15: StepBar[]; c16: StepBar[] } {
|
||||
const ld = stepDevLength(m);
|
||||
const c = COPING_REBAR.cover;
|
||||
const ns = Array.from({ length: 11 }, (_, k) => -B / 2 + c + 137 + 250 * k);
|
||||
const seg = STAIR_SEGMENTS;
|
||||
const top = (i: number) => topEl + seg[i].rise / 1000;
|
||||
const c15Z = top(3) - c / 1000;
|
||||
const c16Z = (i: number) => top(i) - c / 1000 - NOMINAL_DIAMETER.H19 / 1000; // (d_C15 + d_C16)/2 = 19.1
|
||||
const x0c15 = seg[3].from + c;
|
||||
const x1c15 = L - c;
|
||||
const c15: StepBar[] = ns.map((n) => ({ n, x0: x0c15, x1: x1c15, z: c15Z, legs: 'both', leg: ld, length: x1c15 - x0c15 + 2 * ld }));
|
||||
const c16: StepBar[] = [1, 2].flatMap((i) => {
|
||||
const x0 = seg[i].from + c;
|
||||
const x1 = seg[i].to - c + ld; // the horizontal run runs on past the segment end by l_d
|
||||
return ns.map((n): StepBar => ({ n, x0, x1, z: c16Z(i), legs: 'start', leg: ld, length: x1 - x0 + ld }));
|
||||
});
|
||||
return { c15, c16 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 삿갓 (upper cap) of C9–C11 (코핑철근.md §4.3). l_cap is open (Q-K5); recommended here: l_cap = h − c (top cover 100),
|
||||
* so that l_cap stays below h (RB-K04). Cap length = 1740 + 2·l_cap.
|
||||
@@ -152,7 +296,7 @@ export function shearBarsMulti(coping: number, columns: number[]): ShearBars & {
|
||||
return { ...one, between: [] };
|
||||
}
|
||||
const { spacingL, spacingS, levelWidth } = COPING_REBAR;
|
||||
const kMax = Math.floor(levelWidth / 2 / spacingL) + 1;
|
||||
const kMax = Math.ceil(levelWidth / 2 / spacingL); // k_max = ceil((W_L/2)/s_L), spec §3.1
|
||||
const groups = columns.map((xc) =>
|
||||
Array.from({ length: 2 * kMax + 1 }, (_, i) => Math.round(xc + (i - kMax) * spacingL)),
|
||||
);
|
||||
|
||||
+86
-33
@@ -18,11 +18,8 @@ export const FOOTING_DEFAULTS = {
|
||||
/** B10 높이 간격 s_v and 조립 단 배수 n. */
|
||||
sV: 200,
|
||||
n: 2,
|
||||
/** B1·B3 leg and B5·B7 leg (proposed values; Q-B5 open). */
|
||||
legB1: 300,
|
||||
legB3: 230,
|
||||
legB5: 343,
|
||||
legB7: 230,
|
||||
/** 상단 최소 여유 e_top (B10 top level, §7.1). */
|
||||
eTop: 150,
|
||||
} as const;
|
||||
|
||||
export interface GridResult {
|
||||
@@ -47,6 +44,9 @@ export function grid(side: number, s: number = FOOTING_DEFAULTS.s, cSide: number
|
||||
|
||||
const d = (size: BarSize) => NOMINAL_DIAMETER[size];
|
||||
|
||||
/** Leg of B1·B3·B5·B7: 12·d_b on each end (proposed rule, spec §1.2 · Q-B5). */
|
||||
export const legLength = (size: BarSize) => 12 * d(size);
|
||||
|
||||
/** Layer heights (§4.2), mm from the 하면. H = footing height. */
|
||||
export interface Layers {
|
||||
B5: number;
|
||||
@@ -90,9 +90,9 @@ export function bottomBars(L: number, W: number, H: number): FootingBar[] {
|
||||
const l0 = L - 2 * FOOTING_DEFAULTS.cSide;
|
||||
const leg2 = legB2(H);
|
||||
return [
|
||||
{ name: 'B1', size: 'S25', count: N2, length: l0 + 2 * FOOTING_DEFAULTS.legB1 },
|
||||
{ name: 'B1', size: 'S25', count: N2, length: l0 + 2 * legLength('S25') },
|
||||
{ name: 'B2', size: 'S25', count: N2, length: l0 + 2 * leg2 },
|
||||
{ name: 'B3', size: 'S19', count: N2, length: l0 + 2 * FOOTING_DEFAULTS.legB3 },
|
||||
{ name: 'B3', size: 'S19', count: N2, length: l0 + 2 * legLength('S19') },
|
||||
{ name: 'B4', size: 'S25', count: N4, length: l0 },
|
||||
];
|
||||
}
|
||||
@@ -107,28 +107,50 @@ export function crossBars(L: number, W: number, H: number): FootingBar[] {
|
||||
const z = layers(H);
|
||||
const leg6 = z.B5 - z.B68;
|
||||
return [
|
||||
{ name: 'B5', size: 'S29', count: N6, length: l0 + 2 * FOOTING_DEFAULTS.legB5 },
|
||||
{ name: 'B5', size: 'S29', count: N6, length: l0 + 2 * legLength('S29') },
|
||||
{ name: 'B6', size: 'S29', count: N6, length: l0 + 2 * leg6 },
|
||||
{ name: 'B7', size: 'S19', count: N6, length: l0 + 2 * FOOTING_DEFAULTS.legB7 },
|
||||
{ name: 'B7', size: 'S19', count: N6, length: l0 + 2 * legLength('S19') },
|
||||
{ name: 'B8', size: 'S29', count: N8, length: l0 },
|
||||
];
|
||||
}
|
||||
|
||||
/** B10 levels: m = floor((z_B5 − e_top − c_b)/s_v) (§7.1), so the count is m + 1 (9 for P1, H 2000). */
|
||||
export function b10Levels(H: number): number {
|
||||
const m = Math.floor((layers(H).B5 - FOOTING_DEFAULTS.eTop - FOOTING_DEFAULTS.cBottom) / FOOTING_DEFAULTS.sV);
|
||||
return m + 1;
|
||||
}
|
||||
|
||||
/** Depth of a side bar from the side face: c_s + (d_a + d_b)/2 (§7, §8.3). B10-1 hangs on B6, B10-2 on B2. */
|
||||
export const sideDepth = (dOuter: number, dB10: number) => FOOTING_DEFAULTS.cSide + (dOuter + dB10) / 2;
|
||||
|
||||
/** B10-1 (on W sides) and B10-2 (on L sides): m + 1 levels at s_v, two faces each (§7). */
|
||||
export function sideBars(L: number, W: number): FootingBar[] {
|
||||
const depth1 = 123.85; // B10-1 depth from the side
|
||||
const depth2 = 122.25; // B10-2 depth from the side
|
||||
const levels = 9; // 8 @ 200 (기본)
|
||||
export function sideBars(L: number, W: number, H: number): FootingBar[] {
|
||||
const depth1 = sideDepth(NOMINAL_DIAMETER.S29, NOMINAL_DIAMETER.S19); // B10-1: 123.85
|
||||
const depth2 = sideDepth(NOMINAL_DIAMETER.S25, NOMINAL_DIAMETER.S19); // B10-2: 122.25
|
||||
const levels = b10Levels(H);
|
||||
const hook = hookLength('main90', d('S19'));
|
||||
return [
|
||||
{ name: 'B10-1', size: 'S19', count: levels * 2, length: L - 2 * depth2 + 2 * hook },
|
||||
{ name: 'B10-2', size: 'S19', count: levels * 2, length: W - 2 * depth1 + 2 * hookLength('main90', d('S19')) },
|
||||
{ name: 'B10-2', size: 'S19', count: levels * 2, length: W - 2 * depth1 + 2 * hook },
|
||||
];
|
||||
}
|
||||
|
||||
/** Assembly levels j = n, 2n, … strictly inside the B10 levels, 0 < j < m (§8.1). */
|
||||
export function assemblyJs(H: number): number[] {
|
||||
const m = b10Levels(H) - 1;
|
||||
const js: number[] = [];
|
||||
for (let j = FOOTING_DEFAULTS.n; j < m; j += FOOTING_DEFAULTS.n) js.push(j);
|
||||
return js;
|
||||
}
|
||||
|
||||
/** Ends of B11 / B12 lie (d_B10 + d_assembly)/2 further inside than the B10 depth, so the two bars just touch (§8.3). */
|
||||
export function assemblyEndOffset(): number {
|
||||
return (d('S19') + d('S16')) / 2; // 17.5
|
||||
}
|
||||
|
||||
/** B11 (along W, between the B10-2 levels) and B12 (along L), 4s apart with a 2s stagger per level (§8). */
|
||||
export function assemblyBars(L: number, W: number): FootingBar[] {
|
||||
const levelsJ = [2, 4, 6]; // j = n, 2n, 3n … inside the B10 levels (n = 2)
|
||||
export function assemblyBars(L: number, W: number, H: number): FootingBar[] {
|
||||
const levelsJ = assemblyJs(H);
|
||||
const perLevel = (n: number, startOdd: number, startEven: number) =>
|
||||
levelsJ.map((_, idx) => {
|
||||
const i0 = idx % 2 === 0 ? startOdd : startEven;
|
||||
@@ -139,31 +161,63 @@ export function assemblyBars(L: number, W: number): FootingBar[] {
|
||||
// B11 is spread along L (n = 79) and B12 along W (n = 61); each level has its own count.
|
||||
const b11 = perLevel(nL, 0, 2).reduce((a, b) => a + b, 0);
|
||||
const b12 = perLevel(nW, 0, 2).reduce((a, b) => a + b, 0);
|
||||
const hook = hookLength('main90', d('S16'));
|
||||
const hook = Math.round(hookLength('main90', d('S16'))); // 246 mm, rounded as in the document (§8.3)
|
||||
const depth1 = sideDepth(NOMINAL_DIAMETER.S29, NOMINAL_DIAMETER.S19);
|
||||
const depth2 = sideDepth(NOMINAL_DIAMETER.S25, NOMINAL_DIAMETER.S19);
|
||||
return [
|
||||
{ name: 'B11', size: 'S16', count: b11, length: W - 2 * 141.35 + 2 * hook },
|
||||
{ name: 'B12', size: 'S16', count: b12, length: L - 2 * 139.75 + 2 * hook },
|
||||
{ name: 'B11', size: 'S16', count: b11, length: W - 2 * (depth1 + assemblyEndOffset()) + 2 * hook },
|
||||
{ name: 'B12', size: 'S16', count: b12, length: L - 2 * (depth2 + assemblyEndOffset()) + 2 * hook },
|
||||
];
|
||||
}
|
||||
|
||||
/** Plane positions of the grid along one side, mm from the footing centre: x_i = −side/2 + c_s + e + i·s (§3.1). */
|
||||
export function gridPositions(side: number): number[] {
|
||||
const g = grid(side);
|
||||
return Array.from({ length: g.positions }, (_, i) => -side / 2 + FOOTING_DEFAULTS.cSide + g.e + i * FOOTING_DEFAULTS.s);
|
||||
}
|
||||
|
||||
/** B9 bar of the rule (§9.1, Q-B18): a vertical bar at (x, n), mm from the footing centre. */
|
||||
export interface B9Bar {
|
||||
x: number;
|
||||
n: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* B9 전단철근 (확대기초철근 (1).md §9). Wraps B5 (top outer) and B2·B4 (bottom outer), H16 @500 staggered.
|
||||
* Plane positions and hooks are proposed rules (Q-B18, Q-B19).
|
||||
* B9 positions (확대기초철근 (1).md §9.1, rule applied): rows at W index 1, 5, 9 … (step 4 on the grid); in row r the L index
|
||||
* starts at 1 (even r) or 3 (odd r) and steps by 4. A bar is left out when it falls in a column zone: its centre is closer
|
||||
* than the column radius plus half a bar to a column axis (Q-B20, applied: the 기둥 구역 is removed).
|
||||
*/
|
||||
export function shearB9(L: number, W: number, H: number): { rows: number; perRow: number[]; count: number; length: number } {
|
||||
const nW = grid(W).n; // 61
|
||||
const nL = grid(L).n; // 79
|
||||
export function b9Bars(L: number, W: number, columnX: number[], columnD: number = 2000): { rows: number; perRow: number[]; bars: B9Bar[] } {
|
||||
const xs = gridPositions(L);
|
||||
const ns = gridPositions(W);
|
||||
const rowIndices: number[] = [];
|
||||
for (let i = 1; i <= nW; i += 4) rowIndices.push(i);
|
||||
// Even-numbered rows start at i_L = 1, odd-numbered rows at i_L = 3 (2s stagger).
|
||||
const perRow = rowIndices.map((_, r) => {
|
||||
for (let i = 1; i < ns.length; i += 4) rowIndices.push(i);
|
||||
const halfBar = NOMINAL_DIAMETER.H16 / 2;
|
||||
const bars: B9Bar[] = [];
|
||||
const perRow = rowIndices.map((iW, r) => {
|
||||
const start = r % 2 === 0 ? 1 : 3;
|
||||
return Math.floor((nL - start) / 4) + 1;
|
||||
let kept = 0;
|
||||
for (let iL = start; iL < xs.length; iL += 4) {
|
||||
const x = xs[iL];
|
||||
const n = ns[iW];
|
||||
if (columnX.some((cx) => Math.hypot(x - cx, n) < columnD / 2 + halfBar)) continue;
|
||||
bars.push({ x, n });
|
||||
kept++;
|
||||
}
|
||||
return kept;
|
||||
});
|
||||
const count = perRow.reduce((a, b) => a + b, 0);
|
||||
return { rows: rowIndices.length, perRow, bars };
|
||||
}
|
||||
|
||||
/**
|
||||
* B9 전단철근 (확대기초철근 (1).md §9). Vertical bars from the B2·B4 level to the B5 level, H16, with 135° hooks at both ends.
|
||||
* Length = l_v + 2·l_h (l_v = z_B5 − z_B24). The hook tail (6·d) turns toward the footing centre (Q-B19, applied).
|
||||
*/
|
||||
export function shearB9(L: number, W: number, H: number, columnX: number[] = [], columnD: number = 2000): { rows: number; perRow: number[]; count: number; length: number; bars: B9Bar[] } {
|
||||
const { rows, perRow, bars } = b9Bars(L, W, columnX, columnD);
|
||||
const lv = layers(H).B5 - layers(H).B24; // 1750
|
||||
const lh = hookLength('stirrup135', d('H16'));
|
||||
return { rows: rowIndices.length, perRow, count, length: lv + 2 * lh };
|
||||
return { rows, perRow, count: bars.length, length: lv + 2 * lh, bars };
|
||||
}
|
||||
|
||||
/** Leg offsets from the grid line (§5.3, §6.3): the B1/B3 and B5/B7 legs sit beside the outer leg. */
|
||||
@@ -183,11 +237,10 @@ export function legOffsets(): { B1: number; B3: number; B5: number; B7: number }
|
||||
}
|
||||
|
||||
/** B11/B12 heights (§8.1): B10 levels at 150 + k·s_v; the assembly levels are j = n, 2n, 3n (n = 2), and B12 sits d_B10 above B11. */
|
||||
export function assemblyHeights(): { B11: number[]; B12: number[] } {
|
||||
export function assemblyHeights(H: number): { B11: number[]; B12: number[] } {
|
||||
const { cBottom } = FOOTING_DEFAULTS;
|
||||
const sV = FOOTING_DEFAULTS.sV;
|
||||
const n = FOOTING_DEFAULTS.n;
|
||||
const js = [n, 2 * n, 3 * n];
|
||||
const js = assemblyJs(H);
|
||||
const B11 = js.map((j) => cBottom + j * sV);
|
||||
const B12 = B11.map((z) => z + NOMINAL_DIAMETER.S19);
|
||||
return { B11, B12 };
|
||||
|
||||
+41
-19
@@ -1,7 +1,7 @@
|
||||
import type { PierModel } from '../geometry';
|
||||
import { bearingBlockRebar, totals } from './bearing-block';
|
||||
import { barLength, bearingBlockRebar, totals } from './bearing-block';
|
||||
import { bottomPiece, topBarLength, widthLayout } from './coping-main';
|
||||
import { shearBars, shearBarsMulti, sideRows, C14_COUNTS, c14Total, STEP_BAR_COUNTS, shearULength, SIDE_LENGTHS, STEP_LENGTHS, capLength, PIECES_PER_POSITION } from './coping';
|
||||
import { shearBars, shearBarsMulti, copingSideBars, SIDE_HOOK_LEG, c14Bars, c14Length, stairBars, stepDevLength, undersideAt, shearULength, capLength, PIECES_PER_POSITION } from './coping';
|
||||
import { columnRebar, COLUMN_P1 } from './column';
|
||||
import { bottomBars, crossBars, sideBars, assemblyBars, shearB9 } from './footing';
|
||||
import { DEFAULT_MATERIAL, isAssumedMaterial, Material, splitPieces } from './common';
|
||||
@@ -98,14 +98,14 @@ interface FootingBar {
|
||||
}
|
||||
|
||||
/** The footing bars of one footing (B1–B12 and B9), with the notes used in the 철근표. */
|
||||
function footingBarList(Lf: number, Wf: number, Hf: number): FootingBar[] {
|
||||
function footingBarList(Lf: number, Wf: number, Hf: number, columnX: number[]): FootingBar[] {
|
||||
const out: FootingBar[] = [];
|
||||
for (const b of bottomBars(Lf, Wf, Hf)) out.push({ ...b, note: '교각축' });
|
||||
for (const b of crossBars(Lf, Wf, Hf)) out.push({ ...b, note: '교각축 직각' });
|
||||
for (const b of sideBars(Lf, Wf)) out.push({ ...b, note: '측면 수평' });
|
||||
for (const b of assemblyBars(Lf, Wf)) out.push({ ...b, note: '조립 수평' });
|
||||
const b9 = shearB9(Lf, Wf, Hf);
|
||||
out.push({ name: 'B9', size: 'H16', count: b9.count, length: b9.length, note: `전단 @500 엇갈림, ${b9.rows}행 (위치·갈고리는 Q-B18·B19 제안)` });
|
||||
for (const b of sideBars(Lf, Wf, Hf)) out.push({ ...b, note: '측면 수평' });
|
||||
for (const b of assemblyBars(Lf, Wf, Hf)) out.push({ ...b, note: '조립 수평' });
|
||||
const b9 = shearB9(Lf, Wf, Hf, columnX);
|
||||
out.push({ name: 'B9', size: 'H16', count: b9.count, length: b9.length, note: `전단 @500 엇갈림, ${b9.rows}행, 기둥 구역 제외 (위치·갈고리는 Q-B18·B19 규칙 적용)` });
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -121,9 +121,20 @@ export function rebarSummary(model: PierModel, m: Material = DEFAULT_MATERIAL):
|
||||
const B = model.copingB * 1000;
|
||||
|
||||
// Bearing blocks (§6.3 · plan C2)
|
||||
const blk = totals(bearingBlockRebar(model));
|
||||
rows.push({ member: 'bearing', name: 'S1', size: 'H19', count: blk.S1, length: 2500, note: '블럭 배치 분리, 블럭당 8본' });
|
||||
rows.push({ member: 'bearing', name: 'S2', size: 'H19', count: blk.S2, length: 2500, note: '블럭 배치 분리, 블럭당 8본' });
|
||||
// Embedment in the coping (받침블럭철근.md §4): l_leg − (block height − bar depth), with the model's block heights.
|
||||
const blocks = bearingBlockRebar(model);
|
||||
const blk = totals(blocks);
|
||||
const embedments = (name: 'S1' | 'S2') => blocks.flatMap((b) => b.bars.filter((r) => r.name === name).map((r) => r.embedment));
|
||||
const emb1 = embedments('S1');
|
||||
const emb2 = embedments('S2');
|
||||
const range = (xs: number[]) => `${Math.round(Math.min(...xs))}–${Math.round(Math.max(...xs))}`;
|
||||
const anchorage = stepDevLength(m); // RB-S04: the embedment must reach the H19 development length
|
||||
const shortest = Math.min(...emb1, ...emb2);
|
||||
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)` });
|
||||
|
||||
// Coping (코핑철근.md §5–§8)
|
||||
const width = widthLayout(B);
|
||||
@@ -157,17 +168,26 @@ export function rebarSummary(model: PierModel, m: Material = DEFAULT_MATERIAL):
|
||||
if (nCol > 1) {
|
||||
warnings.push({ code: 'RB-M01', message: `다주형 ${nCol}기둥: C14는 T형 기준으로 두었습니다 (기둥마다 반복 규칙 미정, Q-K11).`, target: 'coping' });
|
||||
}
|
||||
const side = sideRows(41.288, 42.538);
|
||||
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(' · ')} (합 ${c14Total()})` });
|
||||
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]})` });
|
||||
// Per row: two 일자 (side faces) and two ㄷ (one at each end), so 4 bars per row (코핑철근.md §6.2).
|
||||
const side = copingSideBars(model.copingL * 1000, 41.288, 42.538);
|
||||
const hook = model.copingB * 1000 - 2 * 127 + 2 * SIDE_HOOK_LEG; // ㄷ body across the width + two legs
|
||||
const c12Range = side.c12.length ? `${Math.round(side.c12[0].straight)}–${Math.round(side.c12[side.c12.length - 1].straight)}` : '—';
|
||||
rows.push({ member: 'coping', name: 'C12', size: 'H25', count: 4 * side.c12.length, length: null, note: `측면, 경사 하면에서 끊김, 일자 ${c12Range} (${2 * side.c12.length}) · ㄷ ${Math.round(hook)} (${2 * side.c12.length})` });
|
||||
rows.push({ member: 'coping', name: 'C13', size: 'H25', count: 4 * side.c13.length, length: side.c13.length ? side.c13[0].straight : null, note: `측면, 끝면까지, 일자 (${2 * side.c13.length}) · ㄷ ${Math.round(hook)} (${2 * side.c13.length})` });
|
||||
// C14 / C15 / C16 are derived from the model (코핑철근.md §7–8); the document's 38 / 11 / 22 and 3852 / 4070 / 4069 are checked in the tests.
|
||||
const L14 = model.copingL * 1000;
|
||||
const c14 = c14Bars(L14, model.copingRefTop, [...side.c12, ...side.c13], (x) => undersideAt(L14, x, model.endBottom, model.levelBottom));
|
||||
const lineCount = (line: number) => c14.filter((b) => b.line === line).length;
|
||||
const omitted = c14.filter((b) => !b.legs).length;
|
||||
rows.push({ member: 'coping', name: 'C14', size: 'H19', count: c14.length, length: c14Length(model.copingB * 1000), note: `위 2 줄 ${lineCount(1)} · ${lineCount(2)}본씩, 아래 ${[3, 4, 5, 6].map(lineCount).join(' · ')} (합 ${c14.length}) · 연직 발 생략 ${omitted}개 (하면 밖)` });
|
||||
const stairs = stairBars(model.copingL * 1000, model.copingB * 1000, model.copingRefTop);
|
||||
rows.push({ member: 'coping', name: 'C15', size: 'H19', count: stairs.c15.length, length: stairs.c15[0].length, note: '계단부, 가장 높은 구간 (거꾸로 U)' });
|
||||
rows.push({ member: 'coping', name: 'C16', size: 'H19', count: stairs.c16.length, length: stairs.c16[0].length, note: `계단부, 3·4구간 (${Math.round(stairs.c16[0].length)} / ${Math.round(stairs.c16[stairs.c16.length - 1].length)})` });
|
||||
|
||||
// Column (기둥철근.md · plan C4), P1 defaults
|
||||
const col = columnRebar(COLUMN_P1, m);
|
||||
rows.push({ member: 'column', name: 'P1', size: 'S32', count: col.mainP1, length: null, note: '외측 r 875, 마디 4697.5 + 갈고리 525 / 3000 ×3 / 3147 (코핑 밖 마디는 기둥철근.md §3.2)' });
|
||||
rows.push({ member: 'column', name: 'P2', size: 'S32', count: col.mainP2, length: null, note: '내측 r 843.2, 마디 4847.5 / 3000 ×3 / 2389' });
|
||||
rows.push({ member: 'column', name: 'P1', size: 'S32', count: col.mainP1, length: null, note: `외측 r ${col.r1}, 마디 ${col.p1Pieces.join(' / ')} (첫 마디에 갈고리 ${col.p1Hook}, 기둥철근.md §3.2)` });
|
||||
rows.push({ member: 'column', name: 'P2', size: 'S32', count: col.mainP2, length: null, note: `내측 r ${col.r2}, 마디 ${col.p2Pieces.join(' / ')}` });
|
||||
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: 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개' });
|
||||
@@ -182,7 +202,9 @@ export function rebarSummary(model: PierModel, m: Material = DEFAULT_MATERIAL):
|
||||
// the length is the longest of them (the largest footing governs the bar).
|
||||
const agg = new Map<string, FootingBar>();
|
||||
for (const f of model.footings) {
|
||||
for (const b of footingBarList(f.length * 1000, f.width * 1000, (f.top - f.bottom) * 1000)) {
|
||||
// Columns on this footing, as x mm from the footing centre (for the B9 column-zone rule).
|
||||
const columnX = model.columns.filter((c) => Math.abs(c.s - f.s) <= f.length / 2).map((c) => (c.s - f.s) * 1000);
|
||||
for (const b of footingBarList(f.length * 1000, f.width * 1000, (f.top - f.bottom) * 1000, columnX)) {
|
||||
const cur = agg.get(b.name);
|
||||
if (cur) {
|
||||
cur.count += b.count;
|
||||
|
||||
Reference in new issue
Block a user