Files
Pier_v1/src/view/footing-section-l-svg.ts
T

154 lines
11 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 { PierModel } from '../core/geometry';
import { NOMINAL_DIAMETER } from '../core/rebar/common';
import { assemblyGridIndices, assemblyJs, b10Levels, b9Bars, grid, gridPositions, layers, legLength, sideDepth } from '../core/rebar/footing';
import { DEFAULT_FOOTING_REBAR } from '../core/rebar/footing-settings';
/**
* 확대기초 · 교각축 단면 (길이 L) (승인 2026-10-27, 2단계 탭). A cut across the footing length: horizontal = L, vertical = height from the 하면.
* Bars that run along W cross this plane as points; bars that run along L lie in it as lines (방향 교환 후 배치).
* - B1–B4 (along W, spaced along L): points at the L grid (odd → B1·B2·B3, even → B4)
* - B5–B8 (along L): one line per family from cover to cover
* - B9: vertical lines at the L grid columns
* - B10-2 (on the L faces): points per level (lowest removed); B10-1 (on the W faces, along L): lines per level (lowest removed)
* - B11 (along W, spaced along L): points on the L grid per assembly level; B12 (along L, spaced along W): lines per assembly level
* Every mark comes from the same functions as the rebar table and the 3D view.
*/
const COLOR = { outline: '#55606E', b5: '#0F5C66', b14: '#B4531F', b9: '#6B3FA0', b10: '#2E7D32', b11: '#55606E', b12: '#55606E', dim: '#B42318', column: '#B08D57', pile: '#8898aa' };
const L_PX = 380;
const X0 = 60;
const Y0 = 230;
export function footingSectionLSvg(model: PierModel): string {
const foot = model.footings[0];
if (!foot) return '<svg viewBox="0 0 600 430" width="600" height="430" role="img" aria-label="확대기초 없음"><text x="20" y="180" font-size="12">확대기초 없음</text></svg>';
const fs = model.footingRebar ?? DEFAULT_FOOTING_REBAR;
const L = foot.length * 1000;
const H = (foot.top - foot.bottom) * 1000;
const k = L_PX / L;
const cx = X0 + L_PX / 2;
const x = (mm: number) => cx + mm * k;
const y = (z: number) => Y0 - z * k;
const z = layers(H, fs);
const parts: string[] = [];
const line = (cls: string, bar: string, x1: number, x2: number, yy: number, color: string, width = 1.4, dash = '') =>
`<line class="${cls}" data-bar="${bar}" x1="${x1.toFixed(1)}" x2="${x2.toFixed(1)}" y1="${yy.toFixed(1)}" y2="${yy.toFixed(1)}" stroke="${color}" stroke-width="${width}"${dash ? ` stroke-dasharray="${dash}"` : ''}/>`;
const dot = (cls: string, bar: string, xx: number, yy: number, color: string, r = 2.6) =>
`<circle class="${cls}" data-bar="${bar}" cx="${xx.toFixed(1)}" cy="${yy.toFixed(1)}" r="${r}" fill="${color}"/>`;
const vline = (cls: string, bar: string, xx: number, y1: number, y2: number, color: string) =>
`<line class="${cls}" data-bar="${bar}" x1="${xx.toFixed(1)}" x2="${xx.toFixed(1)}" y1="${y1.toFixed(1)}" y2="${y2.toFixed(1)}" stroke="${color}" stroke-width="1"/>`;
const text = (xx: number, yy: number, s: string, anchor = 'start', fill = COLOR.outline) =>
`<text x="${xx.toFixed(1)}" y="${yy.toFixed(1)}" font-size="10" font-family="JetBrains Mono, monospace" fill="${fill}" stroke="#fff" stroke-width="3" paint-order="stroke" text-anchor="${anchor}">${s}</text>`;
const tick = (xx: number, yy: number) => `<line x1="${(xx - 3).toFixed(1)}" x2="${(xx + 3).toFixed(1)}" y1="${(yy + 3).toFixed(1)}" y2="${(yy - 3).toFixed(1)}" stroke="${COLOR.dim}" stroke-width="0.8"/>`;
// Context: the column (centred, above the footing) and the piles (below, one stub per distinct L position).
const colD = model.columnDiameterMm;
parts.push(`<rect class="column" x="${x(-colD / 2).toFixed(1)}" y="${y(H) - 36}" width="${(colD * k).toFixed(1)}" height="36" fill="#F2E8D5" stroke="${COLOR.column}" stroke-width="0.8"/>`);
const piles = model.piles.filter((p) => Math.abs(p.s - foot.s) <= foot.length / 2 + 1e-6);
for (const s of [...new Set(piles.map((p) => Math.round((p.s - foot.s) * 1000)))]) {
const d = (piles.find((p) => Math.round((p.s - foot.s) * 1000) === s)?.diameter ?? 0.6) * 1000;
parts.push(`<rect class="pile" x="${(x(s) - d * k / 2).toFixed(1)}" y="${Y0}" width="${Math.max(3, d * k).toFixed(1)}" height="26" fill="${COLOR.pile}" stroke="#3b4656" stroke-width="0.6"/>`);
}
// Footing outline and the 하면 line.
parts.push(`<rect x="${x(-L / 2).toFixed(1)}" y="${y(H).toFixed(1)}" width="${(L * k).toFixed(1)}" height="${(H * k).toFixed(1)}" fill="none" stroke="${COLOR.outline}" stroke-width="1.2"/>`);
parts.push(line('base', '', x(-L / 2), x(L / 2), y(0), COLOR.outline, 1));
// Cover lines on the L faces (측면 피복 c_s).
const xa = x(-L / 2 + fs.cSide);
const xb = x(L / 2 - fs.cSide);
// B5–B8 (along L, 주철근): one line per family from cover to cover, with the legs at both ends
// (B5 ⊓ down, B6 and B7 ⊔ up to the B5 height / up, B8 straight; 확대기초철근 (1).md §6).
const legW = 1.6;
parts.push(line('b5', 'B5', xa, xb, y(z.B5), COLOR.b5, 2));
parts.push(line('b6', 'B6', xa, xb, y(z.B68), COLOR.b5, 2));
parts.push(line('b7', 'B7', xa, xb, y(z.B7), COLOR.b5, 2));
parts.push(line('b8', 'B8', xa, xb, y(z.B68), COLOR.b5, 2));
const leg = (name: string, xx: number, y1: number, y2: number) =>
`<line class="mainleg" data-bar="${name}" x1="${xx.toFixed(1)}" x2="${xx.toFixed(1)}" y1="${y1.toFixed(1)}" y2="${y2.toFixed(1)}" stroke="${COLOR.b5}" stroke-width="${legW}"/>`;
for (const xx of [xa, xb]) {
parts.push(leg('B5', xx, y(z.B5), y(z.B5 - legLength('S29'))));
parts.push(leg('B6', xx, y(z.B68), y(z.B5)));
parts.push(leg('B7', xx, y(z.B7), y(z.B7 + legLength('S19'))));
}
// B1–B4 (along W, spaced along L): points on the L grid. Odd positions carry B1, B2, B3; even carry B4 (as bottomBars).
const xsL = gridPositions(L, fs, 'sL');
xsL.forEach((mm, i) => {
const xx = x(mm);
if (i % 2 === 1) {
parts.push(dot('b1', 'B1', xx, y(z.B1), COLOR.b14));
parts.push(dot('b2', 'B2', xx, y(z.B24), COLOR.b14));
parts.push(dot('b3', 'B3', xx, y(z.B3), COLOR.b14));
} else {
parts.push(dot('b4', 'B4', xx, y(z.B24), COLOR.b14));
}
});
// B9: one vertical line per L column of the B9 grid.
const b9 = b9Bars(L, foot.width * 1000, [], 2000, fs);
for (const xm of [...new Set(b9.bars.map((b) => b.x))]) parts.push(vline('b9', 'B9', x(xm), y(z.B24), y(z.B5), COLOR.b9));
// B10-2 (on the L faces, points) and B10-1 (on the W faces, lines along L), per level above the lowest.
const levels = b10Levels(H, fs);
const dB10 = NOMINAL_DIAMETER.S19;
const depth2 = sideDepth(NOMINAL_DIAMETER.S25, NOMINAL_DIAMETER.S19, fs);
for (let j = 1; j < levels; j++) {
const zj = fs.cBottom + j * fs.sV;
parts.push(dot('b10', 'B10-2', x(-L / 2 + depth2), y(zj), COLOR.b10, 2.2));
parts.push(dot('b10', 'B10-2', x(L / 2 - depth2), y(zj), COLOR.b10, 2.2));
parts.push(line('b10', 'B10-1', x(-L / 2 + depth2), x(L / 2 - depth2), y(zj + dB10), COLOR.b10, 1.2, '2 2'));
}
// B11 (along W, spaced along L): points on the L grid, per assembly level. B12 (along L, spaced along W): lines per level.
const js = assemblyJs(H, fs);
const nL = grid(L, fs, 'sL').positions - 1;
js.forEach((j, idx) => {
const zA = fs.cBottom + j * fs.sV;
for (const i of assemblyGridIndices(nL, fs.kB11, idx)) parts.push(dot('b11', 'B11', x(xsL[i]), y(zA), COLOR.b11, 2.2));
parts.push(line('b12', 'B12', xa, xb, y(zA + dB10), COLOR.b12, 1.2, '6 3'));
});
// Dimensions: the length line with ticks and the grid string, and the right-hand heights.
const g = grid(L, fs, 'sL');
const yDim = Y0 + 40;
parts.push(`<line x1="${x(-L / 2).toFixed(1)}" x2="${x(L / 2).toFixed(1)}" y1="${yDim}" y2="${yDim}" stroke="${COLOR.dim}" stroke-width="0.8"/>`);
parts.push(tick(x(-L / 2), yDim) + tick(x(L / 2), yDim));
parts.push(text(x(-L / 2) - 5, yDim + 14, `${fs.cSide}`, 'end', COLOR.dim));
parts.push(text(x(L / 2) + 5, yDim + 14, `${fs.cSide}`, 'start', COLOR.dim));
parts.push(text(x(-L / 2 + fs.cSide) + 2, yDim + 14, `${g.e.toFixed(1)}`, 'start', COLOR.dim));
parts.push(text(x(L / 2 - fs.cSide) - 2, yDim + 14, `${g.e.toFixed(1)}`, 'end', COLOR.dim));
parts.push(text(cx + 150, yDim - 6, `${g.n}@${fs.sL}=${g.G}`, 'middle', COLOR.dim));
parts.push(text(cx, 20, `교각축 단면 · 길이 ${L.toFixed(0)}`, 'middle', '#1F2937'));
const xr = X0 + L_PX + 18;
parts.push(`<line x1="${xr}" x2="${xr}" y1="${y(H).toFixed(1)}" y2="${y(0).toFixed(1)}" stroke="${COLOR.dim}" stroke-width="0.8"/>`);
parts.push(tick(xr, y(H)) + tick(xr, y(0)));
parts.push(text(xr + 6, y(H) + 12, `${fs.cTop}`, 'start', COLOR.dim));
parts.push(text(xr + 6, (y(H) + y(0)) / 2, `${levels - 1}@${fs.sV}=${(levels - 1) * fs.sV}`, 'start', COLOR.dim));
parts.push(text(xr + 6, y(0) - 4, `${fs.cBottom}`, 'start', COLOR.dim));
// Bubbles outside the outline with a leader to a point of their family.
const bubble = (name: string, bx: number, by: number, tx: number, ty: number, dotColor: string) => {
const edgeY = by < ty ? by + 11 : by - 11;
return `<g class="bubble" data-bubble="${name}"><line class="leader" data-bar="${name}" x1="${bx.toFixed(1)}" x2="${tx.toFixed(1)}" y1="${edgeY.toFixed(1)}" y2="${ty.toFixed(1)}" stroke="${COLOR.outline}" stroke-width="0.7"/><circle class="leader-point" data-bar="${name}" cx="${tx.toFixed(1)}" cy="${ty.toFixed(1)}" r="2.6" fill="${dotColor}"/><circle cx="${bx.toFixed(1)}" cy="${by.toFixed(1)}" r="11" fill="#fff" stroke="${COLOR.outline}"/><text x="${bx.toFixed(1)}" y="${(by + 3).toFixed(1)}" font-size="9" text-anchor="middle" fill="${COLOR.outline}">${name}</text></g>`;
};
const yTop = 40;
const zB11 = js.length > 0 ? fs.cBottom + js[0] * fs.sV : fs.cBottom;
const zB12 = zB11 + dB10;
parts.push(bubble('B5', xa + 12, yTop, xa + 12, y(z.B5), COLOR.b5));
parts.push(bubble('B12', xb - 80, yTop, xb, y(zB12), COLOR.b12));
parts.push(bubble('B11', xb - 12, yTop + 0, x(xsL[xsL.length - 2]), y(zB11), COLOR.b11));
parts.push(bubble('B10-2', X0 - 18, y(fs.cBottom + (levels - 1) * fs.sV / 2), x(-L / 2 + depth2), y(fs.cBottom + (levels - 1) * fs.sV / 2), COLOR.b10));
// Legend and units note.
const yLeg = 352;
parts.push(`<line x1="20" x2="44" y1="${yLeg}" y2="${yLeg}" stroke="${COLOR.b5}" stroke-width="1.6"/>${text(50, yLeg + 3, '주철근 (B5–B8)', 'start', COLOR.outline)}`);
parts.push(`<circle cx="180" cy="${yLeg}" r="2.6" fill="${COLOR.b14}"/>${text(188, yLeg + 3, '직각·보조 (B1–B4)', 'start', COLOR.outline)}`);
parts.push(`<line x1="330" x2="330" y1="${yLeg - 8}" y2="${yLeg + 8}" stroke="${COLOR.b9}" stroke-width="1"/>${text(338, yLeg + 3, '전단·띠 (B9)', 'start', COLOR.outline)}`);
parts.push(`<circle cx="20" cy="${yLeg + 22}" r="2.2" fill="${COLOR.b10}"/>${text(28, yLeg + 25, '측면 수평 (B10-1 · B10-2)', 'start', COLOR.outline)}`);
parts.push(`<line x1="250" x2="274" y1="${yLeg + 22}" y2="${yLeg + 22}" stroke="${COLOR.b11}" stroke-width="1.2" stroke-dasharray="6 3"/>${text(280, yLeg + 25, '조립 (B11·B12)', 'start', COLOR.outline)}`);
parts.push(text(20, yLeg + 48, '치수 mm · 높이 방향은 실제 비율 · 다리 생략 · 기준: 확대기초 하면 0', 'start', COLOR.outline));
return `<svg viewBox="0 0 600 430" width="600" height="430" role="img" aria-label="확대기초 교각축 단면 (길이 L)"><g transform="translate(0 20)">${parts.join('')}</g></svg>`;
}