feat(view): side view (정면) to the right of the footing plan on one scale; 본수 card, plan and side in one row
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 <noreply@anthropic.com>
This commit is contained in:
1 parent
4477bbe52c
commit
a2e4648f7b
3 files changed
+49
-26
No files matched your search
+4
-4
@@ -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'));
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -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; }
|
||||
}
|
||||
+38
-20
@@ -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 `<svg viewBox="${x0} ${y0} ${w} ${h}" width="${pw}" height="${ph}" role="img" aria-label="${label}">${body}</svg>`;
|
||||
}
|
||||
|
||||
/** 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(`<rect x="${f.s - f.length / 2}" y="${-f.width / 2}" width="${f.length}" height="${f.width}" fill="#dfe5ec" stroke="#5b6676" stroke-width="0.02"/>`);
|
||||
@@ -24,42 +37,47 @@ export function footingPlanSvg(model: PierModel): string {
|
||||
for (const c of model.columns) {
|
||||
parts.push(`<circle cx="${c.s}" cy="0" r="${c.diameter / 2}" fill="none" stroke="#0f5c66" stroke-width="0.03" stroke-dasharray="0.1 0.06"/>`);
|
||||
}
|
||||
// Pier axis (dash-dot) through the footing centre.
|
||||
parts.push(`<line x1="${x0}" y1="0" x2="${x1}" y2="0" stroke="#b4531f" stroke-width="0.02" stroke-dasharray="0.3 0.1 0.05 0.1"/>`);
|
||||
// Dimension labels: length and width, in mm.
|
||||
parts.push(`<line x1="${x0}" y1="0" x2="${x0 + w}" y2="0" stroke="#b4531f" stroke-width="0.02" stroke-dasharray="0.3 0.1 0.05 0.1"/>`);
|
||||
parts.push(`<text x="${f.s}" y="${-f.width / 2 - 0.35}" font-size="0.28" text-anchor="middle">${mm(f.length)}</text>`);
|
||||
parts.push(`<text x="${f.s + f.length / 2 + 0.35}" y="0" font-size="0.28" text-anchor="start">${mm(f.width)}</text>`);
|
||||
return `<svg viewBox="${x0} ${y0} ${x1 - x0} ${y1 - y0}" role="img" aria-label="확대기초·말뚝 평면"><g transform="scale(1,1)">${parts.join('')}</g></svg>`;
|
||||
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(`<rect x="${f.s - f.length / 2}" y="${-f.top}" width="${f.length}" height="${f.top - f.bottom}" fill="#dfe5ec" stroke="#5b6676" stroke-width="0.02"/>`);
|
||||
for (const p of piles) {
|
||||
parts.push(`<line x1="${p.s}" y1="${-p.top}" x2="${p.s}" y2="${-p.bottom}" stroke="#8898aa" stroke-width="${Math.max(p.diameter, 0.02)}"/>`);
|
||||
const from = Math.max(p.bottom, botEl);
|
||||
const to = Math.min(p.top, f.bottom);
|
||||
if (to > from) parts.push(`<line x1="${p.s}" y1="${-to}" x2="${p.s}" y2="${-from}" stroke="#8898aa" stroke-width="${Math.max(p.diameter, 0.02)}"/>`);
|
||||
}
|
||||
// Column: from the footing top up to the Level underside.
|
||||
const col = model.columns[0];
|
||||
if (col) {
|
||||
parts.push(`<rect x="${col.s - col.diameter / 2}" y="${-topEl}" width="${col.diameter}" height="${topEl - f.top}" fill="#c9d4df" stroke="#0f5c66" stroke-width="0.02"/>`);
|
||||
parts.push(`<rect x="${col.s - col.diameter / 2}" y="${-f.top - COLUMN_STUB}" width="${col.diameter}" height="${COLUMN_STUB}" fill="#c9d4df" stroke="#0f5c66" stroke-width="0.02"/>`);
|
||||
// Break marks on the column stub and at the pile bottom.
|
||||
parts.push(`<line x1="${col.s - col.diameter}" y1="${-f.top - COLUMN_STUB * 0.6}" x2="${col.s + col.diameter}" y2="${-f.top - COLUMN_STUB * 0.6 - 0.2}" stroke="#55606e" stroke-width="0.03"/>`);
|
||||
}
|
||||
if (piles.length) {
|
||||
parts.push(`<line x1="${f.s - f.length / 2}" y1="${-(botEl + 0.3)}" x2="${f.s + f.length / 2}" y2="${-(botEl + 0.3) - 0.2}" stroke="#55606e" stroke-width="0.03"/>`);
|
||||
}
|
||||
// Labels: the footing height and the pile length, in mm.
|
||||
parts.push(`<text x="${f.s + f.length / 2 + 0.35}" y="${-(f.top + f.bottom) / 2}" font-size="0.28" text-anchor="start">h1 ${mm(f.top - f.bottom)}</text>`);
|
||||
if (piles.length) {
|
||||
const pileLen = piles[0].top - piles[0].bottom;
|
||||
parts.push(`<text x="${f.s}" y="${-(tipEl + 0.5)}" font-size="0.28" text-anchor="middle">말뚝 ${mm(pileLen)}</text>`);
|
||||
parts.push(`<text x="${f.s}" y="${-(botEl + 0.9)}" font-size="0.28" text-anchor="middle">말뚝 ${mm(pileLen)} (끊음)</text>`);
|
||||
}
|
||||
return `<svg viewBox="${x0} ${y0} ${x1 - x0} ${y1 - y0}" role="img" aria-label="확대기초·말뚝 측면"><g transform="scale(1,1)">${parts.join('')}</g></svg>`;
|
||||
void pileTop;
|
||||
return svgOf(x0, y0, w, h, '확대기초·말뚝 측면', parts.join(''));
|
||||
}
|
||||
Reference in new issue
Block a user