feat(rebar): add downward 90° hooks (135 mm) to P6 crossties in 3D rebar model

This commit is contained in:
ypshim committed 2026-10-22 17:45:22 +09:00
1 parent 4aa8dbb3cb
commit a2317caf6c
15 files changed
+740 -133

No files matched your search

+86 -33
View File
@@ -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 };