95 lines
6.2 KiB
TypeScript
95 lines
6.2 KiB
TypeScript
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>`;
|
||
}
|