diff --git a/src/main.ts b/src/main.ts index c1fb524..6aec065 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1251,8 +1251,8 @@ function renderColumnSettings(box: HTMLDivElement, pier: Pier): void { } const captions: Record = { A: 'A 일반 · P3 첫 단 · 주철근 P1·P2 · 띠철근 P3 (겹침 표시)', - B: 'B 단부 · P5 첫 단 · 커플러 1개 (θ + Δθ/2)', - C: 'C 확대기초 안 · P4 첫 단 · 커플러 2개 · P6 4본', + B: 'B 확대기초 안 · P4 첫 단 · 반원 2개 · 커플러 2개 · P6 4본', + C: 'C 단부 · P5 첫 단 · 커플러 1개 (θ + Δθ/2)', }; // 단면 A·B·C in one row (승인 2026-10-26): no wrapping, scrolls sideways on a narrow window. const row = el('div'); diff --git a/src/view/column-section-svg.ts b/src/view/column-section-svg.ts index 3ef4e71..f210fb6 100644 --- a/src/view/column-section-svg.ts +++ b/src/view/column-section-svg.ts @@ -1,55 +1,69 @@ import type { ColumnInput, columnRebar } from '../core/rebar/column'; /** - * 기둥 배근 · 단면 A·B·C (승인 2026-10-26, 추천): plan views at the first level of each group. - * A 일반 = P3 첫 단 (no coupler), B 단부 = P5 첫 단 (1 coupler at θ + Δθ/2), C 확대기초 안 = P4 첫 단 (2 semicircles, 2 couplers). - * P1 and P2 share each angle (r 875 and r 843.2). P6 is drawn as four chords through the centre only when a P6 level sits at the section. - * Every value comes from the column result; the section level is the first level of its group (no new calculation). + * 기둥 배근 · 단면 A·B·C (승인 2026-10-26, 삽도 기준): plan views at the first level of each group, in the drawing's order. + * A 일반 = P3 첫 단 (full ring, no coupler), B 확대기초 안 = P4 첫 단 (2 semicircles, 2 couplers), C 단부 = P5 첫 단 (full ring, 1 coupler). + * P1 and P2 share each angle (r 875 and r 843.2). P6 is four chords through the centre only when a P6 level sits at the section. + * Dimensions and labels are read from the column input and result; the section level is the first level of its group (no new calculation). */ export type ColumnSection = 'A' | 'B' | 'C'; -const COLOR = { main: '#0F5C66', tie: '#6B3FA0', crossTie: '#B4531F', coupler: '#0F5C66', guide: '#55606E' }; +const COLOR = { main: '#0F5C66', tie: '#6B3FA0', crossTie: '#B4531F', coupler: '#0F5C66', guide: '#55606E', dim: '#B42318' }; /** P6 chord angles (deg) from 기둥철근 (1).md §7: 0 · 46.96 · 90 · 133.04. */ const P6_ANGLES = [0, 46.96, 90, 133.04]; export function columnSectionSvg(kind: ColumnSection, input: ColumnInput, col: ReturnType): string { const W = 300; const H = 300; - const level = kind === 'A' ? col.p3[0] : kind === 'B' ? col.p5[0] : col.p4[0]; - const title = kind === 'A' ? 'A 일반 · P3' : kind === 'B' ? 'B 단부 · P5' : 'C 확대기초 안 · P4'; + const level = kind === 'A' ? col.p3[0] : kind === 'B' ? col.p4[0] : col.p5[0]; + const title = kind === 'A' ? 'A 일반 · P3' : kind === 'B' ? 'B 확대기초 안 · P4' : 'C 단부 · P5'; if (level === undefined) { return `단 없음`; } const cx = W / 2; const cy = H / 2 + 6; - const k = (H / 2 - 36) / col.tieRadius; + // Scale from the column outline (D/2), so the 2000 dimension and the 875 / 125 offsets fit in the frame. + const k = (H / 2 - 52) / (input.D / 2); const pt = (r: number, deg: number): [number, number] => [cx + r * k * Math.cos((deg * Math.PI) / 180), cy - r * k * Math.sin((deg * Math.PI) / 180)]; const n = input.nDiv; const step = 360 / n; - const parts: string[] = []; - const text = (x: number, yy: number, s: string, anchor = 'start') => - `${s}`; - - // Tie: a full circle (A, B) or two semicircles (C) through the couplers. const R = (col.tieRadius * k).toFixed(1); - if (kind === 'C') { + const parts: string[] = []; + const text = (x: number, yy: number, s: string, anchor = 'start', fill = COLOR.guide) => + `${s}`; + + // Column outline (D) and the dimension lines: D above, c and r1 along the horizontal diameter. + const outer = input.D / 2 * k; + parts.push(``); + const dimY = cy - outer - 14; + parts.push(``); + parts.push(text(cx, dimY - 4, (input.D / 1000).toFixed(3), 'middle', COLOR.dim)); + const axisY = cy + 14; + const p1x = cx - col.r1 * k; + parts.push(text((cx - outer + p1x) / 2, axisY, `${input.D / 2 - col.r1}`, 'middle', COLOR.dim)); + parts.push(text((p1x + cx) / 2, axisY, `${col.r1}`, 'middle', COLOR.dim)); + + // Tie: a full ring (A, C) or two semicircles through the couplers (B). + if (kind === 'B') { const [x1, y1] = pt(col.tieRadius, step / 2); const [x2, y2] = pt(col.tieRadius, step / 2 + 180); parts.push(``); parts.push(``); - for (const deg of [step / 2, step / 2 + 180]) { - const [x, y] = pt(col.tieRadius, deg); - parts.push(``); - } } else { parts.push(``); - if (kind === 'B') { - const [x, y] = pt(col.tieRadius, step / 2); - parts.push(``); - } } - // P6 chords through the centre, only at a P6 level of this section (C). + // Couplers: a filled dot on the tie, with a leader to a small hollow circle outside the column (the 삽도 convention). + const couplerAngles = kind === 'B' ? [step / 2, step / 2 + 180] : kind === 'C' ? [step / 2] : []; + for (const deg of couplerAngles) { + const [x, y] = pt(col.tieRadius, deg); + const [ox, oy] = pt(input.D / 2 + 90, deg); + parts.push(``); + parts.push(``); + parts.push(``); + } + + // P6 chords through the centre, only at a P6 level of this section. if (col.p6Levels.some((z) => Math.abs(z - level) < 1e-6)) { for (const deg of P6_ANGLES) { const [x1, y1] = pt(col.tieRadius, deg); @@ -67,11 +81,14 @@ export function columnSectionSvg(kind: ColumnSection, input: ColumnInput, col: R parts.push(``); } - parts.push(text(8, 16, title)); + // Labels: the spec of each bar group with its live spacing, and the section level. + const spec = kind === 'A' ? `P1·P2 S32 Div.${n} · P3 H19 @${input.ctcP3}` : kind === 'B' ? `P4 H19 @${input.ctcP4}` : `P5 H19 @${input.ctcP5}`; + parts.push(text(8, 16, title, 'start', '#1F2937')); parts.push(text(8, 30, `z = ${level} mm`)); + parts.push(text(8, H - 24, spec, 'start', '#1F2937')); if (kind === 'A') parts.push(text(8, H - 10, `띠철근 둘레 ${col.tieCircumference} · 겹침 ${col.lapP3}`)); - if (kind === 'B') parts.push(text(8, H - 10, `띠철근 둘레 ${col.tieCircumference} · 커플러 1`)); - if (kind === 'C') parts.push(text(8, H - 10, `반원 ${col.p4SemicircleLength} × 2 · 커플러 2`)); + if (kind === 'B') parts.push(text(8, H - 10, `반원 ${col.p4SemicircleLength} × 2 · 커플러 2`)); + if (kind === 'C') parts.push(text(8, H - 10, `띠철근 둘레 ${col.tieCircumference} · 커플러 1`)); return `${parts.join('')}`; } diff --git a/tests/column-section-svg.test.ts b/tests/column-section-svg.test.ts index 4ac6508..8bec6ef 100644 --- a/tests/column-section-svg.test.ts +++ b/tests/column-section-svg.test.ts @@ -19,12 +19,14 @@ describe('기둥 단면 A·B·C (승인 2026-10-26, 추천)', () => { expect(count(svg, 'p6')).toBe(0); }); - it('B 단부: one coupler', () => { - expect(count(columnSectionSvg('B', input, col), 'coupler')).toBe(1); + it('C 단부 (P5): one coupler on a full ring', () => { + const svg = columnSectionSvg('C', input, col); + expect(count(svg, 'coupler')).toBe(1); + expect(count(svg, 'p5')).toBe(1); }); - it('C 확대기초 안: two semicircles, two couplers, and the four P6 chords at its P6 level', () => { - const svg = columnSectionSvg('C', input, col); + it('B 확대기초 안 (P4): two semicircles, two couplers, and the four P6 chords at its P6 level', () => { + const svg = columnSectionSvg('B', input, col); expect(count(svg, 'coupler')).toBe(2); expect(count(svg, 'p4')).toBe(2); expect(count(svg, 'p6')).toBe(4);