Files
Pier_v1/src/view/column-section-svg.ts
T

95 lines
6.2 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 { ColumnInput, columnRebar } from '../core/rebar/column';
/**
* 기둥 배근 · 단면 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', 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<typeof columnRebar>): string {
const W = 300;
const H = 300;
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 `<svg viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" role="img" aria-label="단면 ${kind}"><text x="20" y="150" font-size="12" fill="${COLOR.guide}">단 없음</text></svg>`;
}
const cx = W / 2;
const cy = H / 2 + 6;
// 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 R = (col.tieRadius * k).toFixed(1);
const parts: string[] = [];
const text = (x: number, yy: number, s: string, anchor = 'start', fill = COLOR.guide) =>
`<text x="${x.toFixed(1)}" y="${yy.toFixed(1)}" font-size="11" font-family="JetBrains Mono, monospace" fill="${fill}" text-anchor="${anchor}">${s}</text>`;
// Column outline (D) and the dimension lines: D above, c and r1 along the horizontal diameter.
const outer = input.D / 2 * k;
parts.push(`<circle cx="${cx}" cy="${cy}" r="${outer.toFixed(1)}" fill="none" stroke="${COLOR.guide}" stroke-width="1"/>`);
const dimY = cy - outer - 14;
parts.push(`<line x1="${(cx - outer).toFixed(1)}" x2="${(cx + outer).toFixed(1)}" y1="${dimY.toFixed(1)}" y2="${dimY.toFixed(1)}" stroke="${COLOR.dim}" stroke-width="0.8"/>`);
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(`<path class="p4" d="M ${x1.toFixed(1)} ${y1.toFixed(1)} A ${R} ${R} 0 0 1 ${x2.toFixed(1)} ${y2.toFixed(1)}" fill="none" stroke="${COLOR.tie}" stroke-width="1.6"/>`);
parts.push(`<path class="p4" d="M ${x2.toFixed(1)} ${y2.toFixed(1)} A ${R} ${R} 0 0 1 ${x1.toFixed(1)} ${y1.toFixed(1)}" fill="none" stroke="${COLOR.tie}" stroke-width="1.6" stroke-dasharray="4 2"/>`);
} else {
parts.push(`<circle class="${kind === 'A' ? 'p3' : 'p5'}" cx="${cx}" cy="${cy}" r="${R}" fill="none" stroke="${COLOR.tie}" stroke-width="1.6"/>`);
}
// 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(`<line x1="${x.toFixed(1)}" y1="${y.toFixed(1)}" x2="${ox.toFixed(1)}" y2="${oy.toFixed(1)}" stroke="${COLOR.guide}" stroke-width="0.8"/>`);
parts.push(`<circle class="coupler" cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="3.5" fill="${COLOR.coupler}"/>`);
parts.push(`<circle cx="${ox.toFixed(1)}" cy="${oy.toFixed(1)}" r="4" fill="none" stroke="${COLOR.tie}" stroke-width="1.2"/>`);
}
// 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);
const [x2, y2] = pt(col.tieRadius, deg + 180);
parts.push(`<line class="p6" x1="${x1.toFixed(1)}" y1="${y1.toFixed(1)}" x2="${x2.toFixed(1)}" y2="${y2.toFixed(1)}" stroke="${COLOR.crossTie}" stroke-width="1.2"/>`);
}
}
// Main bars: P1 (r1) and P2 (r2) on the same angles.
for (let i = 0; i < n; i++) {
const deg = i * step;
const [x1, y1] = pt(col.r1, deg);
const [x2, y2] = pt(col.r2, deg);
parts.push(`<circle class="p1" cx="${x1.toFixed(1)}" cy="${y1.toFixed(1)}" r="3" fill="${COLOR.main}"/>`);
parts.push(`<circle class="p2" cx="${x2.toFixed(1)}" cy="${y2.toFixed(1)}" r="2.2" fill="#fff" stroke="${COLOR.main}" stroke-width="1.2"/>`);
}
// 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.p4SemicircleLength} × 2 · 커플러 2`));
if (kind === 'C') parts.push(text(8, H - 10, `띠철근 둘레 ${col.tieCircumference} · 커플러 1`));
return `<svg viewBox="0 0 ${W} ${H}" width="${W}" height="${H}" role="img" aria-label="단면 ${kind}">${parts.join('')}</svg>`;
}