feat(abut): 날개벽 측면 삽도에 흉벽 · 브라켓 단차 · 저판을 함께 그리고 교대 삽도 EL 표기를 EL.xx.xxx로 통일
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
886347471e
commit
41b3165543
7 files changed
+83
-36
No files matched your search
+48
-17
@@ -68,46 +68,77 @@ export interface AbutWingSideDrawing {
|
||||
bands: { name: string; zTopEl: number; zBottomEl: number }[];
|
||||
seatEl: number;
|
||||
footingTopEl: number;
|
||||
/** 흉벽 상단 EL at this wing's end, and the 브라켓 높이 (m): 브라켓 상면 = 흉벽 상단 − 브라켓 높이. */
|
||||
parapetTopEl: number;
|
||||
bracketHeightM: number;
|
||||
/** 단면 위치 t (m, 전면 +): 벽체 배면 · 흉벽 배면 · 벽체 전면, 저판 앞굽 끝 · 뒷굽 끝, 저판 두께. */
|
||||
wallBackM: number;
|
||||
parapetBackM: number;
|
||||
wallFrontM: number;
|
||||
footingFrontM: number;
|
||||
footingBackM: number;
|
||||
footingThicknessM: number;
|
||||
}
|
||||
|
||||
/** 측면 (날개벽 축 방향으로 잘라 본 모습): 벽체 쪽이 오른쪽, 날개벽 끝이 왼쪽. 수평 단 경계와 뒷굽 끝을 표시한다. */
|
||||
/** EL 표기 (2026-11-04): "EL.41.995". */
|
||||
const el = (v: number) => `EL.${v.toFixed(3)}`;
|
||||
|
||||
/**
|
||||
* 측면 (날개벽 축 방향으로 잘라 본 모습): 벽체 쪽이 오른쪽, 날개벽 끝이 왼쪽.
|
||||
* 벽체 · 흉벽 · 브라켓 단차와 저판을 단면으로 함께 그린다. 가로 위치는 t를 날개벽 거리로 바꾼 값(dM = −t / k, k = 뒷굽 / 뒷굽 거리).
|
||||
*/
|
||||
export function abutWingSideSvg(d: AbutWingSideDrawing): string {
|
||||
const k = 40;
|
||||
const els = [...d.topLine.map((p) => p.el), ...d.bottomLine.map((p) => p.el), d.footingTopEl];
|
||||
const kt = Math.abs(d.footingBackM) / d.heelDistanceM; // |t| per m along the wing
|
||||
const dmOf = (tM: number) => -tM / kt;
|
||||
const bracketTop = d.parapetTopEl - d.bracketHeightM;
|
||||
const footingBottom = d.footingTopEl - d.footingThicknessM;
|
||||
const els = [...d.topLine.map((p) => p.el), ...d.bottomLine.map((p) => p.el), d.parapetTopEl, footingBottom];
|
||||
const elMax = Math.max(...els) + 0.8;
|
||||
const elMin = Math.min(...els) - 0.6;
|
||||
const X = (dM: number) => 40 + (d.lengthM - dM) * k; // wall (dM 0) on the right
|
||||
const Y = (el: number) => 14 + (elMax - el) * k;
|
||||
const W = X(-1.2) + 70;
|
||||
const H = Y(elMin) + 26;
|
||||
const elMin = Math.min(...els) - 0.5;
|
||||
const dmRight = dmOf(d.footingFrontM); // 저판 앞굽 끝 (rightmost)
|
||||
const X = (dM: number) => 40 + (d.lengthM - dM) * k; // wall (dM 0) toward the right
|
||||
const Y = (y: number) => 14 + (elMax - y) * k;
|
||||
const W = X(dmRight) + 110;
|
||||
const H = Y(elMin) + 30;
|
||||
const rect = (dm0: number, dm1: number, e0: number, e1: number, style: string) =>
|
||||
poly([[X(dm0), Y(e0)], [X(dm1), Y(e0)], [X(dm1), Y(e1)], [X(dm0), Y(e1)]], style);
|
||||
const outline: [number, number][] = [
|
||||
...d.topLine.map((p) => [X(p.dM), Y(p.el)] as [number, number]),
|
||||
...[...d.bottomLine].reverse().map((p) => [X(p.dM), Y(p.el)] as [number, number]),
|
||||
];
|
||||
const wb = dmOf(d.wallBackM);
|
||||
const pb = dmOf(d.parapetBackM);
|
||||
const wf = dmOf(d.wallFrontM);
|
||||
const parts = [
|
||||
// 벽체 (단면) on the right, 저판 under it.
|
||||
poly([[X(0), Y(d.footingTopEl)], [X(-1.0), Y(d.footingTopEl)], [X(-1.0), Y(d.seatEl)], [X(0), Y(d.seatEl)]], `fill="${WALL}" stroke="${MUTED}"`),
|
||||
poly([[X(Math.min(d.heelDistanceM, d.lengthM)), Y(d.footingTopEl)], [X(-1.2), Y(d.footingTopEl)], [X(-1.2), Y(d.footingTopEl - 0.5)], [X(Math.min(d.heelDistanceM, d.lengthM)), Y(d.footingTopEl - 0.5)]], `fill="#c9d1da" stroke="${MUTED}"`),
|
||||
poly(outline, `fill="${WING}" stroke="#9c8763"`),
|
||||
// 저판 (뒷굽 끝 ~ 앞굽 끝), 벽체 (기초 상면 ~ 교좌면), 브라켓 단차 (벽체 배면 ~ 흉벽 배면, 브라켓 상면까지), 흉벽 (흉벽 상단까지).
|
||||
rect(dmOf(d.footingBackM), dmRight, footingBottom, d.footingTopEl, `fill="#c9d1da" stroke="${MUTED}"`),
|
||||
rect(wb, wf, d.footingTopEl, d.seatEl, `fill="${WALL}" stroke="${MUTED}"`),
|
||||
rect(wb, pb, d.seatEl, bracketTop, `fill="#9fb1c0" stroke="${MUTED}"`),
|
||||
rect(pb, 0, d.seatEl, d.parapetTopEl, `fill="#9fb1c0" stroke="${MUTED}"`),
|
||||
];
|
||||
// 수평 단 경계 (등단면 / 변화단면) as dashed lines across the wing.
|
||||
for (const b of d.bands.slice(0, -1)) {
|
||||
parts.push(`<line x1="${X(d.lengthM).toFixed(1)}" y1="${Y(b.zBottomEl).toFixed(1)}" x2="${X(0).toFixed(1)}" y2="${Y(b.zBottomEl).toFixed(1)}" stroke="${MUTED}" stroke-dasharray="4 3"/>`);
|
||||
parts.push(`<line x1="${X(d.lengthM).toFixed(1)}" y1="${Y(b.zBottomEl).toFixed(1)}" x2="${X(wb).toFixed(1)}" y2="${Y(b.zBottomEl).toFixed(1)}" stroke="${MUTED}" stroke-dasharray="4 3"/>`);
|
||||
}
|
||||
for (const b of d.bands) {
|
||||
const mid = (b.zTopEl + b.zBottomEl) / 2;
|
||||
parts.push(text(X(d.lengthM * 0.45), Y(mid) + 4, b.name, 'middle', MUTED));
|
||||
parts.push(text(X(d.lengthM * 0.55), Y(mid) + 4, b.name, 'middle', MUTED));
|
||||
}
|
||||
parts.push(text((X(d.lengthM) + X(0)) / 2, 11, `${Math.round(d.lengthM * 1000)} (교좌면 끝에서, 날개벽 축 방향)`, 'middle'));
|
||||
if (d.form === 'general') parts.push(text(X(d.lengthM) - 4, (Y(d.topLine[d.topLine.length - 1].el) + Y(d.bottomLine[d.bottomLine.length - 1].el)) / 2 + 4, `${Math.round(d.endHeightM * 1000)}`, 'end'));
|
||||
parts.push(text(X(-1.0) + 4, Y(d.seatEl) + 4, `교좌면 ${d.seatEl.toFixed(3)}`, 'start'));
|
||||
parts.push(text(X(-1.2) + 4, Y(d.footingTopEl) + 14, d.footingTopEl.toFixed(3), 'start'));
|
||||
parts.push(text(X(0) + 4, Y(d.parapetTopEl) + 4, `흉벽 상단 ${el(d.parapetTopEl)}`, 'start'));
|
||||
parts.push(text(X(pb) + 4, Y(bracketTop) - 4, `브라켓 상면 ${el(bracketTop)}`, 'start'));
|
||||
parts.push(text(X(wf) + 4, Y(d.seatEl) + 4, `교좌면 ${el(d.seatEl)}`, 'start'));
|
||||
parts.push(text(X(dmRight) + 4, Y(d.footingTopEl) + 4, el(d.footingTopEl), 'start'));
|
||||
parts.push(text(X(dmRight) + 4, Y(footingBottom) + 4, `기초 하단 ${el(footingBottom)}`, 'start'));
|
||||
if (d.heelDistanceM < d.lengthM) {
|
||||
parts.push(`<line x1="${X(d.heelDistanceM).toFixed(1)}" y1="${Y(d.footingTopEl).toFixed(1)}" x2="${X(d.heelDistanceM).toFixed(1)}" y2="${(Y(d.footingTopEl) + 18).toFixed(1)}" stroke="${INK}"/>`);
|
||||
parts.push(`<line x1="${X(d.heelDistanceM).toFixed(1)}" y1="${Y(footingBottom).toFixed(1)}" x2="${X(d.heelDistanceM).toFixed(1)}" y2="${(Y(footingBottom) + 10).toFixed(1)}" stroke="${INK}"/>`);
|
||||
}
|
||||
parts.push(text(X(Math.min(d.heelDistanceM, d.lengthM)), Y(d.footingTopEl) + 30, `뒷굽 끝 ${d.heelDistanceM.toFixed(3)} m`, 'middle'));
|
||||
parts.push(text(X(Math.min(d.heelDistanceM, d.lengthM)), Y(footingBottom) + 22, `뒷굽 끝 ${d.heelDistanceM.toFixed(3)} m`, 'middle'));
|
||||
return [
|
||||
`<svg viewBox="0 0 ${W.toFixed(0)} ${H.toFixed(0)}" width="100%" role="img" aria-label="${d.name} 측면: 길이 ${Math.round(d.lengthM * 1000)}, 뒷굽 끝 ${d.heelDistanceM.toFixed(3)} m">`,
|
||||
`<svg viewBox="0 0 ${W.toFixed(0)} ${H.toFixed(0)}" width="100%" role="img" aria-label="${d.name} 측면: 길이 ${Math.round(d.lengthM * 1000)}, 흉벽 상단 ${el(d.parapetTopEl)}, 브라켓 상면 ${el(bracketTop)}, 뒷굽 끝 ${d.heelDistanceM.toFixed(3)} m">`,
|
||||
...parts,
|
||||
'</svg>',
|
||||
].join('');
|
||||
|
||||
Reference in new issue
Block a user