From a2e4648f7b179de5b37216530fb28067689fbd5a Mon Sep 17 00:00:00 2001 From: ypshim Date: Fri, 23 Oct 2026 16:10:14 +0900 Subject: [PATCH] =?UTF-8?q?feat(view):=20side=20view=20(=EC=A0=95=EB=A9=B4?= =?UTF-8?q?)=20to=20the=20right=20of=20the=20footing=20plan=20on=20one=20s?= =?UTF-8?q?cale;=20=EB=B3=B8=EC=88=98=20card,=20plan=20and=20side=20in=20o?= =?UTF-8?q?ne=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan and side use the same scale (22 px per m) and share their horizontal extent, so the footing lines up. The side view breaks the column (3 m above the footing) and the piles (4 m below it) so its height stays near the plan's. The row is three columns from 1100 px, the side view drops to its own row between 800 and 1100 px, and it stacks below 800 px. Co-Authored-By: Claude Haiku 5.5 --- src/main.ts | 8 +++--- src/style.css | 9 +++++-- src/view/footing-svg.ts | 58 +++++++++++++++++++++++++++-------------- 3 files changed, 49 insertions(+), 26 deletions(-) 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('')); }