fix(rebar): 측면 수평 C12 = 상단 · C13 = 하단 변화구간 (엑셀 Pier B357–G358)

T형과 다주형 모두 엑셀 Pier 시트 철근표(C12 상단, C13 하단(변화구간))에 맞춰
측면 수평철근 번호를 서로 바꾼다 (사용자 결정 2026-11-06).
- 경사 하면에서 끊기는 아래 단 → C13, 끝면까지 가는 위 단 → C12
- 철근표 위치·비고 문구, 3D, 다주형 생성기가 같은 원천(sideRows)을 따른다
- C14 높이 순서를 유지하도록 측면 단을 아래(C13) → 위(C12) 순서로 넘김
- 테스트 기대값의 번호를 교환 (P1: C12 2단 8개, C13 5단 20개)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Opus 5.5 committed 2026-11-06 14:29:50 +09:00
1 parent fb7b4b021b
commit 2b26b7c095
10 files changed
+52 -50

No files matched your search

+2 -2
View File
@@ -20,8 +20,8 @@ export const SIDE_COVER_M = 0.127;
/**
* Side-row levels (EL m) of C12 / C13 (코핑철근.md §6.1). z_0 = Level underside + 127 (the lowest bottom bar's Level
* height, 41.038 on P1), z_k = z_0 + k·250. The split is by height band:
* C12 rows lie in the taper band (Level underside … end underside, height h2);
* C13 rows lie in the end band (end underside … top, height h1).
* C13 rows (하단 변화구간) lie in the taper band (Level underside … end underside, height h2);
* C12 rows (상단) lie in the end band (end underside … top, height h1). (엑셀 Pier B357–G358, 2026-11-06 결정)
* zBreak = end underside + 127 is where the taper line starts at the end face (P1: 42.538).
*/
export function sideRowLevels(levelBottom: number, endBottom: number, top?: number): { zBottom: number; zBreak: number; zTop?: number } {
+10 -9
View File
@@ -60,16 +60,17 @@ export function shearBars(coping: number, xc = coping / 2, g: CopingGeom = P1_CO
/**
* Side horizontal bars (코핑철근.md §6): seven rows from the bottom at 250 mm.
* Rows below the sloped-bottom break (z_7,end) are C12, the rows at and above it are C13.
* C12 = 상단 (rows at and above the sloped-bottom break z_7,end, to the end face), C13 = 하단 변화구간 (rows below it,
* cut by the sloped bottom) — 엑셀 Pier B357–G358, 2026-11-06 결정 (was the other way round).
*/
/**
* Side rows on the 250 grid from zBottom (코핑철근.md §6.1). With zTop the grid runs up to zTop (상면 여유 400 mm);
* without it the old seven rows are used. Rows below zBreak are C12 (taper band), the rest C13 (end band).
* without it the old seven rows are used. Rows below zBreak are C13 (taper band, 하단 변화구간), the rest C12 (end band, 상단).
*/
export function sideRows(zBottom: number, zBreak: number, zTop?: number, rows = 7): { c12: number[]; c13: number[] } {
const count = zTop === undefined ? rows : Math.floor((zTop - zBottom + 1e-6) / (COPING_REBAR.spacingSide / 1000)) + 1;
const z = Array.from({ length: Math.max(0, count) }, (_, i) => Math.round((zBottom + i * COPING_REBAR.spacingSide / 1000) * 1000) / 1000);
return { c12: z.filter((v) => v < zBreak - 1e-9), c13: z.filter((v) => v >= zBreak - 1e-9) };
return { c12: z.filter((v) => v >= zBreak - 1e-9), c13: z.filter((v) => v < zBreak - 1e-9) };
}
/** C14 counts (코핑철근.md §8): 2 upper lines of 11 plus the lower rows 5 / 6 / 3 / 2 = 38. */
@@ -103,8 +104,8 @@ export function shearULength(x: number, coping: number, g: CopingGeom = P1_COPIN
return SHEAR_U.flat + 2 * shearUHeight(x, coping, g);
}
/** C13 sits 127 in from each end face (코핑철근.md §6.2). */
export const SIDE_C13_END = 127;
/** The upper rows (C12, 상단) sit 127 in from each end face (코핑철근.md §6.2). */
export const SIDE_C12_END = 127;
/** Leg of each ㄷ형 bar along the side, 1.3·l_d = 1525 (Q-K9). */
export const SIDE_HOOK_LEG = 1525;
@@ -120,8 +121,8 @@ export interface SideBar {
/**
* 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).
* C13 (하단 변화구간) meets the sloped bottom at x_w = c + (z_break − z_k)·(x_L0 − c)/h2, with x_L0 = L/2 − W_L/2.
* C12 (상단) 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, g: CopingGeom = P1_COPING_GEOM, zTop?: number): { c12: SideBar[]; c13: SideBar[] } {
const c = COPING_REBAR.cover;
@@ -129,8 +130,8 @@ export function copingSideBars(L: number, zBottom: number, zBreak: number, g: Co
const bar = (z: number, xw: number): SideBar => ({ z, xw, straight: L - 2 * xw });
const rows = sideRows(zBottom, zBreak, zTop);
return {
c12: rows.c12.map((z) => bar(z, c + ((zBreak - z) * 1000 * (xL0 - c)) / g.h2)),
c13: rows.c13.map((z) => bar(z, SIDE_C13_END)),
c12: rows.c12.map((z) => bar(z, SIDE_C12_END)),
c13: rows.c13.map((z) => bar(z, c + ((zBreak - z) * 1000 * (xL0 - c)) / g.h2)),
};
}
+1 -1
View File
@@ -111,7 +111,7 @@ export function multicolCopingBars(model: PierModel, material: Material = DEFAUL
else intervals.push([a, b]);
}
sideRows.push({ z, intervals });
const name = z < levels.zBreak - 1e-9 ? 'C12' : 'C13', face = B / 2 - 0.127;
const name = z < levels.zBreak - 1e-9 ? 'C13' : 'C12', face = B / 2 - 0.127;
for (const [a, b] of intervals) {
add(name, 'H16', [[a, z, -face], [b, z, -face]]);
add(name, 'H16', [[a, z, face], [b, z, face]]);
+6 -6
View File
@@ -63,8 +63,8 @@ const META: Record<string, Pick<RebarRow, 'spacing' | 'actual' | 'position' | 'd
C9: { spacing: '250', actual: '250', position: '전단 좌 경사', direction: '연직', shape: '폐합 (하부 U + 상부 삿갓)', basis: 'assumed' },
C10: { spacing: '300', actual: '300', position: '전단 Level', direction: '연직', shape: '폐합 (하부 U + 상부 삿갓)', basis: 'assumed' },
C11: { spacing: '250', actual: '250', position: '전단 우 경사', direction: '연직', shape: '폐합 (하부 U + 상부 삿갓)', basis: 'assumed' },
C12: { spacing: '250', actual: '250', position: '측면 하단 (경사 하면에서 끊김)', direction: '교각축', shape: '일자 + 좌우 ㄷ형', basis: 'calc' },
C13: { spacing: '250', actual: '250', position: '측면 상단 (끝면까지)', direction: '교각축', shape: '일자 + 좌우 ㄷ형', basis: 'calc' },
C12: { spacing: '250', actual: '250', position: '측면 상단 (끝면까지)', direction: '교각축', shape: '일자 + 좌우 ㄷ형', basis: 'calc' },
C13: { spacing: '250', actual: '250', position: '측면 하단 변화구간 (경사 하면에서 끊김)', direction: '교각축', shape: '일자 + 좌우 ㄷ형', basis: 'calc' },
C14: { spacing: '1000 / 2000', actual: '1000', position: '직각 수평 (위 2줄 · 아래 엇갈림)', direction: '교각축 직각', shape: '⊓ (폭 방향)', basis: 'calc' },
C15: { spacing: '250', actual: '250', position: '계단부 가장 높은 5구간', direction: '교각축', shape: '거꾸로 된 U', basis: 'calc' },
C16: { spacing: '250', actual: '250', position: '계단부 3·4구간', direction: '교각축', shape: 'L형', basis: 'calc' },
@@ -276,14 +276,14 @@ export function rebarSummary(
const levels = sideRowLevels(model.levelBottom, model.endBottom, model.copingRefTop);
const side = copingSideBars(model.copingL * 1000, levels.zBottom, levels.zBreak, model.copingGeom, levels.zTop);
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)}` : '—';
const c13Range = side.c13.length ? `${Math.round(side.c13[0].straight)}–${Math.round(side.c13[side.c13.length - 1].straight)}` : '—';
// Per bar: 2 일자 (straight) and 2 ㄷ (hook body + legs), so 4 bars per row position.
const sidePieces = (bars: { straight: number }[]) => bars.flatMap((b) => [{ length: b.straight, count: 2 }, { length: hook, count: 2 }]);
rows.push({ member: 'coping', name: 'C12', size: 'H25', count: 4 * side.c12.length, length: null, pieces: sidePieces(side.c12), note: `측면, 경사 하면에서 끊김, 일자 ${c12Range} (${2 * side.c12.length}) · ㄷ ${Math.round(hook)} (${2 * side.c12.length}) · 도면 6단 중 하면 단(EL ${(model.levelBottom + 0.127).toFixed(3)})은 하면 주철근 C7·C8로 계산 (문서 §6.1)` });
rows.push({ member: 'coping', name: 'C13', size: 'H25', count: 4 * side.c13.length, length: null, pieces: sidePieces(side.c13), note: `측면, 끝면까지, 일자 (${2 * side.c13.length}) · ㄷ ${Math.round(hook)} (${2 * side.c13.length})` });
rows.push({ member: 'coping', name: 'C12', size: 'H25', count: 4 * side.c12.length, length: null, pieces: sidePieces(side.c12), note: `측면 상단, 끝면까지, 일자 (${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: null, pieces: sidePieces(side.c13), note: `측면 하단 변화구간, 경사 하면에서 끊김, 일자 ${c13Range} (${2 * side.c13.length}) · ㄷ ${Math.round(hook)} (${2 * side.c13.length}) · 도면 하단 단 중 하면 단(EL ${(model.levelBottom + 0.127).toFixed(3)})은 하면 주철근 C7·C8로 계산 (문서 §6.1)` });
// 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, model.copingGeom.levelWidth), model.copingRebar.mainLayerGap / 1000);
const c14 = c14Bars(L14, model.copingRefTop, [...side.c13, ...side.c12], (x) => undersideAt(L14, x, model.endBottom, model.levelBottom, model.copingGeom.levelWidth), model.copingRebar.mainLayerGap / 1000);
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}개 (하면 밖)` });