Files
Pier_v1/src/core/rebar/column.ts
T
ypshimandClaude Haiku 5.5 65c53f2b5d feat(view,rebar): column diameter D as a free input on ② (1200–3000 mm, 50 mm steps), with three checks
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>
2026-10-23 15:50:52 +09:00

207 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
};
}