feat(rebar): 전·후열 단차 300 mm 이상 블록 보강 C17·C18

전열·후열 상면 차가 300 mm 이상인 구간의 높은 쪽 블록에 (결정 2026-11-10, step-bars.ts):
- C17 ㄷ형 띠 H16 @전단간격: 단차면 + c ~ 측면 − c, 위 = 높은 상면 − c, 다리 낮은 상면 아래 l_d까지
- C18 수평 피부철근 H16 @측면간격: 단차면 안쪽, 낮은 상면 + 측면간격마다
T형(철근표·3D)과 다주형(편 좌표 → 꺾임 접기) 모두에 넣는다. 구간마다 따로 두고 이웃 구간과 잇지 않는다 (가정).

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-10 17:17:23 +09:00
1 parent 8f808f453e
commit 33aa2d9a61
5 files changed
+142 -4

No files matched your search

+4 -1
View File
@@ -8,7 +8,7 @@
*/
import type { PierModel } from '../geometry';
import { DEFAULT_MATERIAL, developmentLength, fyOf, hookLength, type Material, NOMINAL_DIAMETER, spliceLength } from './common';
import { stepBars } from './step-bars';
import { stepBars, stepBlockBars } from './step-bars';
import { copingFold, foldBar, type FoldPiece, inWedge, isTransverse, onMitre } from './coping-fold';
export type CopingPoint = [number, number, number];
@@ -296,6 +296,9 @@ export function multicolCopingInfo(model: PierModel, material: Material = DEFAUL
stepMin: MULTI_REBAR.stepMin * 1000, across: top.filter((_, i) => i % 2 === 0) });
for (const b of stairs.bars) add(b.name, 'H19', b.pts.map(([x, z]): CopingPoint => [x / 1000, z, b.n]));
for (const message of stairs.warnings) warnings.push({ code: 'RB-S01', message });
// 전·후열 단차 300 이상: 높은 쪽 블록에 C17 ㄷ형 띠 (전단간격) · C18 피부철근 (측면간격), H16 (결정 2026-11-10).
for (const b of stepBlockBars(model, { cover: c * 1000, d: d('H16'), ld: Math.round(developmentLength(d('H16'), fy('H16'), material.fck)),
tieSpacing: settings.spacingS, skinSpacing: settings.sideSpacing })) add(b.name, 'H16', b.pts);
// Bent coping (결정 2026-11-10, coping-fold.ts): runs along the axis turn at their mitre point; bars across the width that
// would cut the mitre are left out, and one shear set (the position nearest the kink) is laid on the mitre.
+70
View File
@@ -110,3 +110,73 @@ export function stepBars(model: PierModel, opt: StepOptions): { bars: StepBar[];
}
return { bars, warnings };
}
/** 전·후열 단차 블록 보강의 최소 단차 (mm, 결정 2026-11-10). */
export const STEP_BLOCK_MIN_MM = 300;
export interface StepBlockBar {
name: 'C17' | 'C18';
/** Unfolded centre line (g m, EL m, n m). */
pts: [number, number, number][];
/** Developed length (mm). */
length: number;
}
export interface StepBlockOptions {
/** Cover c (mm). */
cover: number;
/** H16 diameter and development length l_d (mm). */
d: number;
ld: number;
/** C17 spacing along the axis = 전단간격, C18 vertical spacing = 측면간격 (mm). */
tieSpacing: number;
skinSpacing: number;
}
/**
* 전·후열 단차 블록 보강 (결정 2026-11-10): 전열·후열 상면 차가 300 mm 이상인 구간에서, 높은 쪽 블록에
* - C17 ㄷ형 띠철근 (H16): 블록 폭 방향 (코핑 안 단차면 + c ~ 측면 − c), 위 = 높은 상면 − c, 다리 양쪽을 낮은 상면 아래 l_d까지.
* 교축 방향으로 전단간격, 구간 시작 + c ~ 끝 − c에 고르게.
* - C18 수평 피부철근 (H16): 단차면 안쪽 (C17 다리 안), 낮은 상면 + 측면간격마다 높은 상면 − c 아래까지, 구간 시작 + c ~ 끝 − c.
* 구간 = 전열·후열 계단 경계를 합친 사이. 구간마다 따로 두고 이웃 구간과 잇지 않는다 (가정).
*/
export function stepBlockBars(model: PierModel, opt: StepBlockOptions): StepBlockBar[] {
const parts = model.copingParts.filter((p) => p.k === 0);
if (parts.length !== 2 || !model.backSegments) return [];
const half = model.copingB / 2;
const c = opt.cover / 1000, ld = opt.ld / 1000, d = opt.d / 1000;
const [front] = parts;
const split = front.nFrom > -half + 1e-9 ? front.nFrom : front.nTo;
const frontDir = Math.sign((front.nFrom + front.nTo) / 2 - split);
const topAt = (segs: { s0: number; s1: number; top: number }[], x: number) =>
(segs.find((s) => x >= s.s0 && x < s.s1) ?? segs[segs.length - 1]).top;
const bounds = [...new Set([0, model.copingL, ...model.segments.map((s) => s.s0), ...model.backSegments.map((s) => s.s0)].map((v) => Math.round(v * 1000) / 1000))].sort((a, b) => a - b);
const len = (pts: [number, number, number][]) => pts.slice(1).reduce((s, p, i) => s + Math.hypot(p[0] - pts[i][0], p[1] - pts[i][1], p[2] - pts[i][2]), 0) * 1000;
const out: StepBlockBar[] = [];
for (let i = 1; i < bounds.length; i++) {
const a = bounds[i - 1], b = bounds[i], mid = (a + b) / 2;
const fTop = topAt(model.segments, mid), bTop = topAt(model.backSegments, mid);
if (Math.abs(fTop - bTop) * 1000 < STEP_BLOCK_MIN_MM - 1e-6) continue;
const backHigh = bTop > fTop;
const dir = backHigh ? -frontDir : frontDir;
const hi = Math.max(fTop, bTop), lo = Math.min(fTop, bTop);
const nA = split + dir * c, nB = dir * (half - c);
const zt = hi - c, legEnd = lo - ld;
const span = b - a - 2 * c;
if (span <= 0) continue;
const s = opt.tieSpacing / 1000;
const count = Math.floor(span / s + 1e-9) + 1;
const start = a + c + (span - (count - 1) * s) / 2;
for (let k = 0; k < count; k++) {
const x = start + k * s;
const pts: [number, number, number][] = [[x, legEnd, nA], [x, zt, nA], [x, zt, nB], [x, legEnd, nB]];
out.push({ name: 'C17', pts, length: len(pts) });
}
const nSkin = nA + dir * d;
for (let z = lo + opt.skinSpacing / 1000; z < zt - 1e-9; z += opt.skinSpacing / 1000) {
const pts: [number, number, number][] = [[a + c, z, nSkin], [b - c, z, nSkin]];
out.push({ name: 'C18', pts, length: len(pts) });
}
}
return out;
}
+18 -2
View File
@@ -4,14 +4,15 @@ import { copingDefaults } from './coping-settings';
import { DEFAULT_COLUMN_REBAR } from './column-settings';
import { DEFAULT_FOOTING_REBAR, type FootingRebarSettings } from './footing-settings';
import { bottomPiece, topBarLength, widthLayout, topSplicePieces } from './coping-main';
import { shearBars, shearBarsMulti, copingSideBars, SIDE_HOOK_LEG, c14Bars, c14Length, tStepBars, stepDevLength, undersideAt, shearULength, capLength, PIECES_PER_POSITION } from './coping';
import { shearBars, shearBarsMulti, copingSideBars, SIDE_HOOK_LEG, c14Bars, c14Length, COPING_REBAR, tStepBars, stepDevLength, undersideAt, shearULength, capLength, PIECES_PER_POSITION } from './coping';
import { sideRowLevels } from '../coping-geom';
import { CAST_PENDING } from './cast-in-place';
import { castRebarLayout } from './cast-layout';
import { multicolCopingInfo } from './multicol-coping';
import { stepBlockBars } from './step-bars';
import { columnRebar, columnInputFor } from './column';
import { bottomBars, crossBars, sideBars, assemblyBars, shearB9 } from './footing';
import { DEFAULT_MATERIAL, FckByMember, fyOf, isAssumedMaterial, Material, materialFor, splitPieces, STANDARD_BAR } from './common';
import { DEFAULT_MATERIAL, developmentLength, FckByMember, fyOf, isAssumedMaterial, Material, materialFor, NOMINAL_DIAMETER, splitPieces, STANDARD_BAR } from './common';
/** 근거 badge (spec §6.1): where the value comes from. */
export type Basis = 'input' | 'default' | 'calc' | 'assumed';
@@ -57,6 +58,7 @@ const MULTI_NOTE: Record<string, string> = {
C9: '전단 · 테이퍼·헌치', C10: '전단 · 기둥 Level', C11: '전단 · 기둥 사이 오목',
C12: '측면 상단 (전 길이), 다리 위치마다', C13: '측면 하단 변화구간 (하면에서 끊김), 다리 위치마다',
C14: '크로스타이 연직 600 · 교각축 1200 엇갈림', C15: '계단부 ⊓ (가장 높은 계단)', C16: '계단부 L형 (그 밖의 계단, 낮은 쪽 다리)',
C17: '전·후열 단차 300 이상 블록 ㄷ형 띠 (전단간격)', C18: '전·후열 단차 블록 수평 피부철근 (측면간격)',
};
/** Table metadata per bar (코핑철근.md, 확대기초철근 (1).md, 기둥철근 (1).md, 받침블럭철근.md). */
@@ -79,6 +81,8 @@ const META: Record<string, Pick<RebarRow, 'spacing' | 'actual' | 'position' | 'd
C14: { spacing: '1000 / 2000', actual: '1000', position: '직각 수평 (위 2줄 · 아래 엇갈림)', direction: '교각축 직각', shape: '⊓ (폭 방향)', basis: 'calc' },
C15: { spacing: '250', actual: '250', position: '계단부 가장 높은 구간', direction: '교각축', shape: '거꾸로 된 U', basis: 'calc' },
C16: { spacing: '250', actual: '250', position: '계단부 그 밖의 구간 (최저 상면 + 100 이상)', direction: '교각축', shape: 'L형', basis: 'calc' },
C17: { spacing: '250', actual: '250', position: '전·후열 단차 300 이상 블록', direction: '교각축 직각', shape: 'ㄷ형', basis: 'calc' },
C18: { spacing: '250', actual: '250', position: '전·후열 단차 블록 단차면', direction: '교각축', shape: '직선', basis: 'calc' },
P1: { spacing: '원주 46등분', actual: '—', position: '외측 r 875', direction: '연직', shape: '아래 끝 90° 갈고리, 커플러 이음', basis: 'calc' },
P2: { spacing: '원주 46등분', actual: '—', position: '내측 r 843.2', direction: '연직', shape: '직선, 커플러 이음', basis: 'calc' },
'주철근 커플러': { spacing: '@3000 (P2 +150)', actual: '—', position: 'P1·P2 각 줄', direction: '원통', shape: 'L 150 · Ø60', basis: 'default' },
@@ -311,6 +315,18 @@ export function rebarSummary(
note: `${name === 'C15' ? '계단부, 가장 높은 구간 (거꾸로 U)' : '계단부, 그 밖의 계단 구간 (L형, 낮은 쪽 다리)'} · ${pieces.map((q) => `${q.length} × ${q.count}`).join(' / ')}` });
}
for (const message of steps.warnings) warnings.push({ code: 'RB-S01', target: 'coping', message });
// 전·후열 단차 블록 보강 C17 · C18 (결정 2026-11-10): 단차 300 mm 이상인 구간만.
const block = stepBlockBars(model, { cover: COPING_REBAR.cover, d: NOMINAL_DIAMETER.H16, ld: Math.round(developmentLength(NOMINAL_DIAMETER.H16, fyOf(mCoping, 'H16'), mCoping.fck)),
tieSpacing: model.copingRebar.spacingS, skinSpacing: model.copingRebar.sideSpacing });
for (const name of ['C17', 'C18'] as const) {
const bars = block.filter((b) => b.name === name);
if (!bars.length) continue;
const byLength = new Map<number, number>();
for (const b of bars) byLength.set(Math.round(b.length), (byLength.get(Math.round(b.length)) ?? 0) + 1);
const pieces = [...byLength].map(([length, count]) => ({ length, count }));
rows.push({ member: 'coping', name, size: 'H16', count: bars.length, length: pieces.length === 1 ? pieces[0].length : null, ...(pieces.length > 1 ? { pieces } : {}),
note: name === 'C17' ? '전·후열 단차 300 이상 블록, ㄷ형 띠 (다리 낮은 상면 아래 l_d)' : '전·후열 단차 블록, 단차면 수평 피부철근' });
}
}
// Column (기둥철근.md · plan C4), P1 defaults
+7 -1
View File
@@ -8,10 +8,11 @@ import { columnRebar, columnInputFor } from '../core/rebar/column';
import { castRebarLayout } from '../core/rebar/cast-layout';
import { multicolCopingBars } from '../core/rebar/multicol-coping';
import { copingFold, foldBar } from '../core/rebar/coping-fold';
import { stepBlockBars } from '../core/rebar/step-bars';
import { barDepths, barsAcross } from '../core/rebar/bearing-block';
import { blockFramePoint } from '../core/geometry';
import { INTEGRATED_BLOCK } from '../core/integrated-block';
import { bendRadius, DEFAULT_MATERIAL, FckByMember, hookLength, Material, materialFor, NOMINAL_DIAMETER } from '../core/rebar/common';
import { bendRadius, DEFAULT_MATERIAL, developmentLength, FckByMember, fyOf, hookLength, Material, materialFor, NOMINAL_DIAMETER } from '../core/rebar/common';
import { diameterGroupIndex, DIAMETER_GROUPS, memberOfBar, type RebarMemberKey } from '../core/rebar/color';
import { stretchMatrix } from './scene';
@@ -307,6 +308,11 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL,
tBar(b.name, 0, [mm(b.pts[i - 1][0]), b.pts[i - 1][1], n], [mm(b.pts[i][0]), b.pts[i][1], n], NOMINAL_DIAMETER.H19, COLOR.coping);
}
}
// 전·후열 단차 300 이상 블록 보강 C17 · C18 (결정 2026-11-10, step-bars.ts).
const blockLd = Math.round(developmentLength(NOMINAL_DIAMETER.H16, fyOf(mCoping, 'H16'), mCoping.fck));
for (const b of stepBlockBars(m, { cover: COPING_REBAR.cover, d: NOMINAL_DIAMETER.H16, ld: blockLd, tieSpacing: m.copingRebar.spacingS, skinSpacing: m.copingRebar.sideSpacing })) {
for (let i = 1; i < b.pts.length; i++) tBar(b.name, 0, b.pts[i - 1], b.pts[i], NOMINAL_DIAMETER.H16, COLOR.coping);
}
}
// Columns: P1 / P2 main bars on their radii, ties P3 / P5 / P4 as rings, P6 crossties across the column.