diff --git a/src/core/coping-geom.ts b/src/core/coping-geom.ts index 9cd0d8a..5dcac46 100644 --- a/src/core/coping-geom.ts +++ b/src/core/coping-geom.ts @@ -14,11 +14,16 @@ export interface CopingGeom { /** P1 values: 끝단 높이 1000, 테이퍼 높이 1500, Level 폭 2100. Used when the input does not give them. */ export const P1_COPING_GEOM: CopingGeom = { h1: 1000, h2: 1500, levelWidth: 2100 }; +/** Bar centre to the face of the coping, 127 mm (코핑철근.md §6.1, y_e). */ +export const SIDE_COVER_M = 0.127; + /** - * Side-row levels (EL m) of C12 / C13 from the Level underside. 코핑철근.md §6.1: z_0 = 하면 주철근 Level 높이 = 41.038, - * which is the Level underside 40.911 plus 127; z_k = z_0 + k·250, and the break is z_6 = 42.538 (C12 below, C13 above). - * So the rows keep this offset from the Level underside. + * 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). + * zBreak = end underside + 127 is where the taper line starts at the end face (P1: 42.538). */ -export function sideRowLevels(levelBottom: number): { zBottom: number; zBreak: number } { - return { zBottom: levelBottom + 0.377, zBreak: levelBottom + 1.627 }; +export function sideRowLevels(levelBottom: number, endBottom: number): { zBottom: number; zBreak: number } { + return { zBottom: levelBottom + SIDE_COVER_M + 0.25, zBreak: endBottom + SIDE_COVER_M }; } diff --git a/src/core/rebar/summary.ts b/src/core/rebar/summary.ts index 9185f56..60a5200 100644 --- a/src/core/rebar/summary.ts +++ b/src/core/rebar/summary.ts @@ -190,7 +190,7 @@ export function rebarSummary( warnings.push({ code: 'RB-M01', message: `다주형 ${nCol}기둥: C14는 T형 기준으로 두었습니다 (기둥마다 반복 규칙 미정, Q-K11).`, target: 'coping' }); } // Per row: two 일자 (side faces) and two ㄷ (one at each end), so 4 bars per row (코핑철근.md §6.2). - const levels = sideRowLevels(model.levelBottom); + const levels = sideRowLevels(model.levelBottom, model.endBottom); const side = copingSideBars(model.copingL * 1000, levels.zBottom, levels.zBreak, model.copingGeom); 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)}` : '—'; diff --git a/src/view/rebar3d.ts b/src/view/rebar3d.ts index 9103bdf..e0877cf 100644 --- a/src/view/rebar3d.ts +++ b/src/view/rebar3d.ts @@ -204,7 +204,7 @@ export function buildRebar3D(m: PierModel, common: Material = DEFAULT_MATERIAL, // and two ㄷ bars: a body across the width at x_w, 25.4 above the row, with legs 1525 inward along the faces. const sideFace = B / 2 - 0.127; const dSide = NOMINAL_DIAMETER.H25; - const levels = sideRowLevels(m.levelBottom); + const levels = sideRowLevels(m.levelBottom, m.endBottom); const side = copingSideBars(L * 1000, levels.zBottom, levels.zBreak, m.copingGeom); for (const [name, rows] of [['C12', side.c12], ['C13', side.c13]] as const) { for (const r of rows) { diff --git a/tests/coping-geom.test.ts b/tests/coping-geom.test.ts index 44db75a..df7b2cb 100644 --- a/tests/coping-geom.test.ts +++ b/tests/coping-geom.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest'; import { buildPier } from '../src/core/geometry'; -import { P1_COPING_GEOM, sideRowLevels } from '../src/core/coping-geom'; +import { P1_COPING_GEOM, SIDE_COVER_M, sideRowLevels } from '../src/core/coping-geom'; +import { sideRows } from '../src/core/rebar/coping'; import { shearUHeight, shearULength } from '../src/core/rebar/coping'; import { rebarSummary } from '../src/core/rebar/summary'; import { buildRebar3D } from '../src/view/rebar3d'; @@ -45,7 +46,7 @@ describe('① 코핑 끝단 높이 h1 · 테이퍼 높이 h2 · Level 폭 (1단 }); it('side-row levels keep their P1 values from the Level underside (assumption, 명세 확인 필요)', () => { - const levels = sideRowLevels(40.911); + const levels = sideRowLevels(40.911, 42.411); expect(levels.zBottom).toBeCloseTo(41.288, 6); expect(levels.zBreak).toBeCloseTo(42.538, 6); }); @@ -59,3 +60,28 @@ describe('① 코핑 끝단 높이 h1 · 테이퍼 높이 h2 · Level 폭 (1단 expect(buildRebar3D(changed).byName.get('C10')!.length).toBeGreaterThan(0); }); }); + +describe('C12 in the taper band (h2), C13 in the end band (h1)', () => { + const bands = (input: Parameters[0]) => { + const m = buildPier(input); + const lv = sideRowLevels(m.levelBottom, m.endBottom); + const rows = sideRows(lv.zBottom, lv.zBreak); + return { m, rows }; + }; + it.each([ + ['P1', {}], + ['h1 800', { copingH1Mm: 800 }], + ['h1 1200', { copingH1Mm: 1200 }], + ['h2 1800', { copingH2Mm: 1800 }], + ])('%s: every C12 row is in the taper band and every C13 row in the end band', (_name, change) => { + const { m, rows } = bands({ ...loadP1(), ...change }); + for (const z of rows.c12) { + expect(z).toBeGreaterThanOrEqual(m.levelBottom - 1e-9); + expect(z).toBeLessThan(m.endBottom + SIDE_COVER_M); + } + for (const z of rows.c13) { + expect(z).toBeGreaterThanOrEqual(m.endBottom + SIDE_COVER_M - 1e-9); + expect(z).toBeLessThanOrEqual(m.copingRefTop); + } + }); +});