- 선택: 평면 skew 평행사변형(기본) / 직사각(비활성, 다음 단계), 말뚝 원기둥 D600 / 강관 · PHC(비활성, 규칙 미정) - 평면 삽도 (src/view/abut-footing-svg.ts): 저판, 벽체 윤곽, 교대축, 말뚝 3열(skew만큼 밀림), 열 위치 · 본수, 폭 치수 - 입력: 폭 4600, 두께 1100, 앞굽 1200 (뒷굽 자동 1000), 버림 여유 · 두께 100 / 100, 말뚝 지름 600 (QA-5), 연단 650, 본수 규칙 간격 2000 - 열 위치 "650 · 2000 · 1300 · 650" 입력 (전면 연단 자동, 합 = 폭 검사), 열별 말뚝 본수 표 − / + (0 = 본수 규칙) - 자동 값: 뒷굽, 길이(교좌면 길이), 말뚝 길이(①에서 수정), 선단 EL, 본수 규칙 문구. 시안 메모 - 3D: 교대 버림을 저판 아래에 그림 (부재 표시 "버림" 토글 적용), 말뚝 지름 입력 반영, 말뚝 열을 skew만큼 s 방향으로 밀어 배치 - 예전 저장 파일의 앞굽 끝 · 뒷굽 끝은 폭 · 앞굽으로 바꿔 읽음 - 테스트 추가 · 갱신. 전체 661건 통과, 타입 검사 · 빌드 통과. 브라우저 시각 확인은 미실시 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
60 lines
3.4 KiB
TypeScript
60 lines
3.4 KiB
TypeScript
/**
|
||
* 교대 확대기초 · 말뚝 평면 삽도 (2026-11-04, 시안 ② 확대기초·말뚝 카드). 순수 문자열 함수.
|
||
* 생성본 값으로 그린다: 저판(skew 평행사변형, 굵은 선), 벽체 윤곽(실선), 교대축(점선), 말뚝 3열(원), 열 위치, 폭 치수.
|
||
* 가로(s)와 세로(t) 축척이 같다. 위가 전면, 아래가 배면이다.
|
||
*/
|
||
|
||
export interface AbutFootingDrawing {
|
||
/** 교대축 길이 L (m). */
|
||
lengthM: number;
|
||
/** s per m of t along the wing (skew): s = s_axis + k·t. */
|
||
skewK: number;
|
||
/** t (m): 저판 앞굽 끝, 뒷굽 끝, 벽체 전면, 벽체 배면. */
|
||
footingFrontM: number;
|
||
footingBackM: number;
|
||
wallFrontM: number;
|
||
wallBackM: number;
|
||
/** 말뚝 열: t (m), s (m, 교대축 기준, skew 전), 열 이름 (1열 · 전면 …). */
|
||
rows: { t: number; s: number[]; name: string }[];
|
||
pileDiameterM: number;
|
||
/** 열 위치 치수 (mm, 배면부터): 배면 연단, 간격, 간격, 전면 연단. */
|
||
rowDimsMm: [number, number, number, number];
|
||
footingWidthMm: number;
|
||
}
|
||
|
||
const INK = '#18212b';
|
||
const MUTED = '#55606e';
|
||
const label = (x: number, y: number, s: string, anchor = 'start', fill = INK) =>
|
||
`<text x="${x.toFixed(1)}" y="${y.toFixed(1)}" text-anchor="${anchor}" font-size="11" fill="${fill}" font-family="JetBrains Mono, monospace">${s}</text>`;
|
||
|
||
export function abutFootingPlanSvg(d: AbutFootingDrawing): string {
|
||
const k = 36; // px per m, both directions
|
||
const sMin = Math.min(d.skewK * d.footingFrontM, d.skewK * d.footingBackM) - 0.4;
|
||
const sMax = d.lengthM + Math.max(d.skewK * d.footingFrontM, d.skewK * d.footingBackM) + 0.4;
|
||
const tTop = d.footingFrontM + 0.4;
|
||
const X = (s: number, t: number) => 20 + (s + d.skewK * t - sMin) * k;
|
||
const Y = (t: number) => 14 + (tTop - t) * k;
|
||
const W = 20 + (sMax - sMin) * k + 170;
|
||
const H = Y(d.footingBackM - 0.4) + 34;
|
||
const quad = (t0: number, t1: number, style: string) =>
|
||
`<polygon points="${[[0, t0], [d.lengthM, t0], [d.lengthM, t1], [0, t1]].map(([s, t]) => `${X(s, t).toFixed(1)},${Y(t).toFixed(1)}`).join(' ')}" ${style}/>`;
|
||
const parts: string[] = [];
|
||
parts.push(quad(d.footingBackM, d.footingFrontM, `fill="#e6ebf0" stroke="${INK}" stroke-width="2"`));
|
||
parts.push(quad(d.wallBackM, d.wallFrontM, `fill="none" stroke="${MUTED}"`));
|
||
parts.push(`<line x1="${X(0, 0).toFixed(1)}" y1="${Y(0).toFixed(1)}" x2="${X(d.lengthM, 0).toFixed(1)}" y2="${Y(0).toFixed(1)}" stroke="#c0392b" stroke-dasharray="6 3"/>`);
|
||
parts.push(label(X(d.lengthM, 0) + 6, Y(0) + 4, '교대축', 'start', '#c0392b'));
|
||
const r = Math.max(2, (d.pileDiameterM / 2) * k);
|
||
for (const row of d.rows) {
|
||
for (const s of row.s) parts.push(`<circle cx="${X(s, row.t).toFixed(1)}" cy="${Y(row.t).toFixed(1)}" r="${r.toFixed(1)}" fill="#ffffff" stroke="${INK}"/>`);
|
||
const tLabel = `${row.t >= 0 ? '+' : '−'}${Math.round(Math.abs(row.t) * 1000)}`;
|
||
parts.push(label(X(d.lengthM, row.t) + r + 8, Y(row.t) + 4, `${row.name} · t ${tLabel} · ${row.s.length}본`));
|
||
}
|
||
const [e1, g1, g2, e2] = d.rowDimsMm;
|
||
parts.push(label(20, H - 10, `${d.footingWidthMm} = 뒷굽쪽 ${e1} · ${g1} · ${g2} · ${e2} 전면 · 위 = 전면`, 'start', MUTED));
|
||
return [
|
||
`<svg viewBox="0 0 ${W.toFixed(0)} ${H.toFixed(0)}" width="100%" role="img" aria-label="확대기초 평면: skew 평행사변형, 폭 ${d.footingWidthMm}, 말뚝 ${d.rows.map((r2) => r2.s.length).join(' + ')}본">`,
|
||
...parts,
|
||
'</svg>',
|
||
].join('');
|
||
}
|