diff --git a/src/main.ts b/src/main.ts index 07e835c..175d1f7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -730,12 +730,12 @@ function renderFootingDrawings(box: HTMLDivElement, pier: Pier): void { const plan = el('figure', undefined, 'footing-fig'); plan.innerHTML = footingPlanSvg(pier.model); plan.appendChild(el('figcaption', '평면 · 1열이 아래, 열은 교축과 직각 방향 줄', 'muted small')); - grid.append(cardCol, plan); - box.appendChild(grid); + // The side view (정면) sits to the right of the plan, on the same scale (승인 2026-10-23). const side = el('figure', undefined, 'footing-fig'); side.innerHTML = footingSideSvg(pier.model); - side.appendChild(el('figcaption', '정면 · 점선 = 경사 선택 시 상면 (L2, h2)', 'muted small')); - box.appendChild(side); + side.appendChild(el('figcaption', '정면 · 끊음 표시: 기둥과 말뚝 일부 생략 · 점선 = 경사 선택 시 상면 (L2, h2)', 'muted small')); + grid.append(cardCol, plan, side); + box.appendChild(grid); box.appendChild(el('p', '기초 회전각 θ는 교각축 대비 회전으로, 생성 규칙은 spec에 추가 후 적용합니다 (지금은 0°).', 'muted small')); } diff --git a/src/style.css b/src/style.css index d385ae8..aa56b18 100644 --- a/src/style.css +++ b/src/style.css @@ -800,8 +800,13 @@ body { background: #eef1f4; font-family: 'Noto Sans KR', system-ui, sans-serif; .footing-fig { margin: 0; } .footing-fig svg { width: 100%; height: auto; max-height: 260px; background: #ffffff; border: 1px solid #d5dde6; border-radius: 6px; } -/* ② 말뚝 배치: 열별 본수 카드 (왼쪽) · 평면도 (오른쪽), 측면도는 아래 */ -.footing-grid { display: grid; grid-template-columns: minmax(230px, 320px) minmax(0, 1fr); gap: 12px; align-items: start; margin-top: 12px; } +/* ② 말뚝 배치: 열별 본수 카드 · 평면도 · 정면을 한 줄에 (같은 축척). 1100px 이하는 정면을 아래 줄로, 800px 이하는 한 열로. */ +.footing-grid { display: grid; grid-template-columns: minmax(220px, 280px) auto auto; gap: 12px; align-items: start; margin-top: 12px; } +.footing-grid .footing-fig svg { display: block; max-width: none; } +@media (max-width: 1100px) { + .footing-grid { grid-template-columns: minmax(220px, 280px) auto; } + .footing-grid .footing-fig:last-of-type { grid-column: 1 / -1; } +} @media (max-width: 800px) { .footing-grid { grid-template-columns: 1fr; } } diff --git a/src/view/footing-svg.ts b/src/view/footing-svg.ts index b6ec64a..580e4cd 100644 --- a/src/view/footing-svg.ts +++ b/src/view/footing-svg.ts @@ -1,20 +1,33 @@ /** * 확대기초·말뚝 그림 (2단계, 승인 2026-10-23): plan and side views as SVG strings, drawn from the model (footings, piles, * columns) and the input values. Pure: no DOM, so that it can be tested. Units: m in the model; the labels show mm. + * Both views use one scale (FOOTING_PX_PER_M), so they can sit side by side. The side view breaks the column and the piles + * (끊음 표시) so that its height stays near the plan's. */ import type { PierModel } from '../core/geometry'; +export const FOOTING_PX_PER_M = 22; +/** Side view: the column is shown this far above the footing top, the piles this far below the footing bottom (m). */ +const COLUMN_STUB = 3; +const PILE_STUB = 4; + const mm = (v: number) => Math.round(v * 1000); +function svgOf(x0: number, y0: number, w: number, h: number, label: string, body: string): string { + const pw = Math.round(w * FOOTING_PX_PER_M); + const ph = Math.round(h * FOOTING_PX_PER_M); + return `${body}`; +} + /** Plan view: the footing rectangle (length along the 교축, width across), the pile circles, the column circle, the axis. */ export function footingPlanSvg(model: PierModel): string { const f = model.footings[0]; if (!f) return ''; const pad = 0.8; const x0 = f.s - f.length / 2 - pad; - const x1 = f.s + f.length / 2 + pad; const y0 = -f.width / 2 - pad; - const y1 = f.width / 2 + pad; + const w = f.length + 2 * pad; + const h = f.width + 2 * pad; const piles = model.piles.filter((p) => Math.abs(p.s - f.s) <= f.length / 2 + 1e-6); const parts: string[] = []; parts.push(``); @@ -24,42 +37,47 @@ export function footingPlanSvg(model: PierModel): string { for (const c of model.columns) { parts.push(``); } - // Pier axis (dash-dot) through the footing centre. - parts.push(``); - // Dimension labels: length and width, in mm. + parts.push(``); parts.push(`${mm(f.length)}`); parts.push(`${mm(f.width)}`); - return `${parts.join('')}`; + return svgOf(x0, y0, w, h, '확대기초·말뚝 평면', parts.join('')); } -/** Side view (elevation): the footing, the piles down to their tips, and the column above the footing (EL up). */ +/** Side view (elevation, EL up): the footing, the piles, and the column stub above the footing, both broken (끊음). */ export function footingSideSvg(model: PierModel): string { const f = model.footings[0]; if (!f) return ''; const piles = model.piles.filter((p) => Math.abs(p.s - f.s) <= f.length / 2 + 1e-6); - const tipEl = piles.length ? Math.min(...piles.map((p) => p.bottom)) : f.bottom; - const topEl = model.levelBottom; + const pileTop = piles.length ? Math.max(...piles.map((p) => p.top)) : f.bottom; + const pileLen = piles.length ? piles[0].top - piles[0].bottom : 0; const pad = 0.8; const x0 = f.s - f.length / 2 - pad; - const x1 = f.s + f.length / 2 + pad; - const y0 = -(topEl + pad); - const y1 = -(tipEl - pad); + const w = f.length + 2 * pad; + // Visible band in EL: from the column stub above the footing top, down to the pile stub below the footing bottom. + const topEl = f.top + COLUMN_STUB; + const botEl = f.bottom - PILE_STUB; + const y0 = -topEl; + const h = topEl - botEl; const parts: string[] = []; - // Elevation: y = −EL so that the level grows upward on the screen. parts.push(``); for (const p of piles) { - parts.push(``); + const from = Math.max(p.bottom, botEl); + const to = Math.min(p.top, f.bottom); + if (to > from) parts.push(``); } - // Column: from the footing top up to the Level underside. const col = model.columns[0]; if (col) { - parts.push(``); + parts.push(``); + // Break marks on the column stub and at the pile bottom. + parts.push(``); + } + if (piles.length) { + parts.push(``); } - // Labels: the footing height and the pile length, in mm. parts.push(`h1 ${mm(f.top - f.bottom)}`); if (piles.length) { - const pileLen = piles[0].top - piles[0].bottom; - parts.push(`말뚝 ${mm(pileLen)}`); + parts.push(`말뚝 ${mm(pileLen)} (끊음)`); } - return `${parts.join('')}`; + void pileTop; + return svgOf(x0, y0, w, h, '확대기초·말뚝 측면', parts.join('')); }