D is an input on the column card (default 2000 mm). The 3D column, the piles of a cast-in pier and the column rebar follow it; n_div stays 46. Three checks run on each regenerate and appear in 알림: D below the Level width (2100), D + 2c within the coping width, and the 주철근 arc spacing ≥ max(1.5·d_b, 40). Spec §5.2 records the rule (approved 2026-10-23). Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
207 lines
8.3 KiB
TypeScript
207 lines
8.3 KiB
TypeScript
import type { PierModel } from '../geometry';
|
||
import { DEFAULT_MATERIAL, developmentLength, fyOf, hookLength, Material, NOMINAL_DIAMETER, spliceLength } from './common';
|
||
import { layers } from './footing';
|
||
|
||
/**
|
||
* 기둥 철근 C4 (plan C4). Source: RAG/기둥철근 (1).md, 2026-10-22 최종 정리. Type-1 원형 중실 기둥.
|
||
* Lengths in mm, heights in mm from the 확대기초 상면 (up +).
|
||
*/
|
||
export interface ColumnInput {
|
||
/** 기둥 지름 D. */
|
||
D: number;
|
||
/** 기둥 높이 H_c (확대기초 상면 ~ 코핑 Level 하면). */
|
||
Hc: number;
|
||
/** 기둥 중심 위치의 코핑 높이 h_cop. */
|
||
hcop: number;
|
||
/** 기둥 면 ~ P1 중심 피복 c. */
|
||
c: number;
|
||
/** P1 위 끝의 코핑 상면 아래 깊이 e_P1. */
|
||
eP1: number;
|
||
/** 띠철근 구간 위 끝의 코핑 상면 아래 깊이 e_top. */
|
||
eTop: number;
|
||
/** 확대기초 깊이 H_f (for P4). */
|
||
Hf: number;
|
||
/** 주철근 원주 등분 수 n_div. */
|
||
nDiv: number;
|
||
/** 주철근 커플러 간격 (확대기초 상면부터). */
|
||
ctcCoupler: number;
|
||
/** P2 커플러를 P1보다 올리는 높이. */
|
||
dzCp2: number;
|
||
/** 커플러는 P1 위 끝에서 이 거리(mm) 이상 아래에만 둔다 (커플러 여유, 가정 2500). */
|
||
couplerTopMargin?: number;
|
||
ctcP3: number;
|
||
ctcP5: number;
|
||
ctcP4: number;
|
||
/** P4 구간 깊이 비율 r_P4. */
|
||
rP4: number;
|
||
ctcP6: number;
|
||
}
|
||
|
||
/** 예제 운곡교 P1 (기둥철근.md §1.2 기본값). */
|
||
/**
|
||
* Column inputs from the model (1단계 연계, 승인 2026-10-23). Hc = Level 하면 − 확대기초 상면 (기둥철근.md §2: 12.703 on P1);
|
||
* h_cop = 계단 상면(기둥 중심 구간의 상면) − Level 하면 (2.644 on P1, 43.555 − 40.911). Only these two follow the model;
|
||
* the other values stay as in COLUMN_P1.
|
||
*/
|
||
export function columnInputFor(model: PierModel, base: ColumnInput = COLUMN_P1): ColumnInput {
|
||
const col = model.columns[0];
|
||
const seg = model.segments.find((g) => col.s >= g.s0 - 1e-9 && col.s <= g.s1 + 1e-9) ?? model.segments[0];
|
||
const Hc = Math.round((model.levelBottom - model.footings[0].top) * 1000 * 10) / 10;
|
||
const hcop = Math.round((seg.top - model.levelBottom) * 1000 * 10) / 10;
|
||
// C3 (상면 3단) = the lowest coping top −127 (C1 cover) −2·57.2 (layer spacing 상면 주철근 단 간격); P1: 43.170.
|
||
// P1 top = C3 − 100 (계산 2026-10-23), so e_P1 = step top − (C3 − 0.1) = 485 on P1.
|
||
const c3 = model.copingRefTop - 0.127 - 2 * 0.0572;
|
||
const eP1 = Math.round((seg.top - (c3 - 0.1)) * 1000);
|
||
// e_top: the coping middle level (Level 하면 + 계단 상면)/2 is the top of P3 and P6 (승인 2026-10-23).
|
||
const eTop = Math.round((seg.top - (model.levelBottom + seg.top) / 2) * 1000);
|
||
return { ...base, Hc, hcop, eP1, eTop, D: model.columnDiameterMm };
|
||
}
|
||
|
||
export const COLUMN_P1: ColumnInput = {
|
||
D: 2000,
|
||
Hc: 12703,
|
||
hcop: 2644,
|
||
c: 125,
|
||
eP1: 485,
|
||
// Coping middle level: (Level 하면 + 계단 상면)/2 on P1 = 42.233 → e_top 1322 (승인 2026-10-23, Q-C3 대체).
|
||
eTop: 1322,
|
||
Hf: 2000,
|
||
nDiv: 46,
|
||
ctcCoupler: 3000,
|
||
dzCp2: 150,
|
||
ctcP3: 300,
|
||
ctcP5: 120,
|
||
ctcP4: 120,
|
||
rP4: 0.5,
|
||
ctcP6: 1800,
|
||
};
|
||
|
||
/** A bar in the 확대기초 top layer, height from the 확대기초 상면 (mm, negative = below). */
|
||
export interface TopBar {
|
||
name: string;
|
||
z: number;
|
||
d: number;
|
||
}
|
||
|
||
/**
|
||
* RB-C07 (기둥철근.md §6.1–6.2, 결정): a P4 that interferes with a 확대기초 top bar is deleted.
|
||
* Interference: |z_P4 − z_bar| < (d_P4 + d_bar)/2. Only the top layer (B5, B1) is checked; the assembly bars
|
||
* B11/B12 are not, by the 2026-10-22 decision (Q-C9 무시).
|
||
*/
|
||
export function deleteInterferingP4(p4: number[], topBars: TopBar[], dP4: number = NOMINAL_DIAMETER.H19): { kept: number[]; deleted: { z: number; bar: string }[] } {
|
||
const kept: number[] = [];
|
||
const deleted: { z: number; bar: string }[] = [];
|
||
for (const z of p4) {
|
||
const hit = topBars.find((b) => Math.abs(z - b.z) < (dP4 + b.d) / 2);
|
||
if (hit) deleted.push({ z, bar: hit.name });
|
||
else kept.push(z);
|
||
}
|
||
return { kept, deleted };
|
||
}
|
||
|
||
/** Top-layer bars of the footing as heights from its 상면 (B5 and B1, §4.2). */
|
||
export function footingTopBars(Hf: number): TopBar[] {
|
||
const z = layers(Hf);
|
||
return [
|
||
{ name: 'B5', z: z.B5 - Hf, d: NOMINAL_DIAMETER.S29 },
|
||
{ name: 'B1', z: z.B1 - Hf, d: NOMINAL_DIAMETER.S25 },
|
||
];
|
||
}
|
||
|
||
const dS32 = NOMINAL_DIAMETER.S32;
|
||
const dH19 = NOMINAL_DIAMETER.H19;
|
||
const dH16 = NOMINAL_DIAMETER.H16;
|
||
const round = (v: number, d = 1) => Math.round(v * 10 ** d) / 10 ** d;
|
||
|
||
export function columnRebar(x: ColumnInput = COLUMN_P1, m: Material = DEFAULT_MATERIAL) {
|
||
// P1 and P2 main bars (기둥철근.md §3)
|
||
const r1 = x.D / 2 - x.c;
|
||
const r2 = r1 - dS32;
|
||
const zTop1 = x.Hc + x.hcop - x.eP1;
|
||
const lD2 = developmentLength(dS32, fyOf(m, 'S32'), 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;
|
||
// Couplers at k·ctc from the footing, kept at least the margin below the P1 top (커플러 여유).
|
||
const couplerMargin = x.couplerTopMargin ?? 2500;
|
||
const couplerCount = Math.max(0, Math.floor((zTop1 - couplerMargin) / x.ctcCoupler));
|
||
const couplerLevels = Array.from({ length: couplerCount }, (_, 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);
|
||
const mainP2 = mainLines * (couplersP1 + 1);
|
||
const couplers = couplersP1 * x.nDiv * 2;
|
||
|
||
// Ties (기둥철근.md §4–§6)
|
||
// Tie radius r_t = r_1 + (d_P1 + d_P3)/2 (기둥철근.md §4.1)
|
||
const tieRadius = r1 + (dS32 + dH19) / 2;
|
||
const zTopT = x.Hc + x.hcop - x.eTop;
|
||
const lEmin = Math.max(x.D, zTopT / 6, 450);
|
||
const n5 = Math.floor(lEmin / x.ctcP5);
|
||
const lE = Math.max(lEmin, n5 * x.ctcP5 + x.ctcP5 / 2);
|
||
const p3 = Array.from({ length: Math.floor((zTopT - lE) / x.ctcP3) + 1 }, (_, k) => lE + k * x.ctcP3);
|
||
const n5all = Math.floor(lE / x.ctcP5);
|
||
const p5 = Array.from({ length: n5all }, (_, j) => lE - (j + 1) * x.ctcP5);
|
||
const z5last = lE - n5all * x.ctcP5;
|
||
const m4 = Math.floor((z5last + x.rP4 * x.Hf) / x.ctcP4);
|
||
const p4All = Array.from({ length: m4 }, (_, j) => z5last - (j + 1) * x.ctcP4);
|
||
const p4Check = deleteInterferingP4(p4All, footingTopBars(x.Hf));
|
||
const p4 = p4Check.kept;
|
||
|
||
// P6 crossties (기둥철근.md §7): 4 bars per 단, from the first kept P4
|
||
const z4first = p4.length ? p4[0] : z5last;
|
||
const p6Levels: number[] = [];
|
||
// P6 runs up to the same top as P3, the coping middle level (승인 2026-10-23).
|
||
for (let z = z4first; z <= zTopT; z += x.ctcP6) p6Levels.push(z);
|
||
const p6Straight = 2 * tieRadius;
|
||
const p6Hook = hookLength('tie90', dH16);
|
||
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, fyOf(m, 'H19'), m.fck);
|
||
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,
|
||
zTop1: round(zTop1),
|
||
zTop2: round(zTop2),
|
||
zBot: round(zBot, 2),
|
||
p1Hook,
|
||
p1Pieces,
|
||
p2Pieces,
|
||
couplerP1: couplerLevels,
|
||
couplerP2: couplerLevels.map((z) => z + x.dzCp2),
|
||
mainP1,
|
||
mainP2,
|
||
couplers,
|
||
tieRadius: round(tieRadius, 2),
|
||
tieCircumference: round(2 * Math.PI * tieRadius, 1),
|
||
zTopT: round(zTopT),
|
||
lEmin: round(lEmin),
|
||
lE: round(lE),
|
||
p3,
|
||
p5,
|
||
p4,
|
||
p6Levels,
|
||
p6Count: p6Levels.length * 4,
|
||
p6Length: round(p6Length, 1),
|
||
p4SemicircleLength: round(Math.PI * tieRadius, 1),
|
||
p3Count: p3.length,
|
||
p5Count: p5.length,
|
||
p4Count: p4.length,
|
||
p4Deleted: p4Check.deleted,
|
||
};
|
||
}
|