diff --git a/src/main.ts b/src/main.ts
index 0cc8a50..0977d9e 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1094,6 +1094,14 @@ function fillAbutWingCard(
bands: wing.bands,
seatEl: rec.seatEl,
footingTopEl: members.footingTopEl,
+ parapetTopEl: wing.topStartEl,
+ bracketHeightM: members.shape.bracketHeightM,
+ wallBackM: members.shape.wallBackM,
+ parapetBackM: members.shape.parapetBackM,
+ wallFrontM: members.shape.wallFrontM,
+ footingFrontM: members.shape.footingFrontM,
+ footingBackM: members.shape.footingBackM,
+ footingThicknessM: members.shape.footingThicknessM,
}),
`${sideKo(side)} 날개벽 측면 · ${NAME[wing.form]} (날개벽 축 방향으로 잘라 본 모습) · 축척 같음`,
);
diff --git a/src/view/abut-bearing-svg.ts b/src/view/abut-bearing-svg.ts
index 81f8a9a..35267ab 100644
--- a/src/view/abut-bearing-svg.ts
+++ b/src/view/abut-bearing-svg.ts
@@ -78,8 +78,8 @@ export function abutBearingSectionSvg(d: AbutBearingDetail): string {
text(right, at(d.h1 + d.h2 + d.h3 / 2) + 4, `h3 ${d.h3}`, 'start'),
text(right, at(E - d.h4 / 2) + 4, `h4 ${d.h4}`, 'start'),
text(cx, at(E) - 10, `E = ${E}`),
- text(24, base + 16, `교좌면 ${d.seatEl.toFixed(3)}`, 'start', MUTED),
- text(24, at(d.h1 + d.h2) - 4, `z ${d.z.toFixed(3)}`, 'start', MUTED),
+ text(24, base + 16, `교좌면 EL.${d.seatEl.toFixed(3)}`, 'start', MUTED),
+ text(24, at(d.h1 + d.h2) - 4, `z EL.${d.z.toFixed(3)}`, 'start', MUTED),
text(cx, 244, '교축 방향 · 높이 2배 과장', 'middle', MUTED),
'',
].join('');
diff --git a/src/view/abut-wall-svg.ts b/src/view/abut-wall-svg.ts
index 9ab81b3..d56117a 100644
--- a/src/view/abut-wall-svg.ts
+++ b/src/view/abut-wall-svg.ts
@@ -35,6 +35,8 @@ const CONC = '#d9c9a8';
const CONC_DARK = '#c7b48d';
const FOOT = '#b9c3ce';
const t3 = (v: number) => v.toFixed(3);
+/** EL 표기 (2026-11-04): "EL.41.995". */
+const el = (v: number) => `EL.${v.toFixed(3)}`;
const label = (x: number, y: number, s: string, anchor = 'middle', fill = INK) =>
`${s}`;
const poly = (pts: [number, number][], style: string) => ``;
@@ -75,12 +77,12 @@ export function abutWallSectionSvg(d: AbutWallDrawing): string {
``,
label(X(0), 14, '교대축', 'middle', '#c0392b'),
label((X(d.wallBackM) + X(d.wallFrontM)) / 2, Y(top) - 18, `${d.bracketWidthMm} + ${d.parapetThicknessMm} + ${d.seatWidthMm} = ${d.bracketWidthMm + d.parapetThicknessMm + d.seatWidthMm}`),
- label(X(0) + 6, Y(top) - 4, `흉벽 상단 ${t3(top)}`, 'start'),
- label(X(d.wallBackM) - 6, Y(bracket) + 4, `브라켓 상면 ${t3(bracket)}`, 'end'),
- label(X(d.wallFrontM) + 6, Y(seg.seatEl) + 4, `교좌면 ${t3(seg.seatEl)}`, 'start'),
+ label(X(0) + 6, Y(top) - 4, `흉벽 상단 ${el(top)}`, 'start'),
+ label(X(d.wallBackM) - 6, Y(bracket) + 4, `브라켓 상면 ${el(bracket)}`, 'end'),
+ label(X(d.wallFrontM) + 6, Y(seg.seatEl) + 4, `교좌면 ${el(seg.seatEl)}`, 'start'),
label(X(d.wallFrontM) + 6, Y((seg.seatEl + d.footingTopEl) / 2) + 4, `벽체 ${(seg.seatEl - d.footingTopEl).toFixed(3)}`, 'start'),
- label(X(d.footingFrontM) + 6, Y(d.footingTopEl) + 4, `상면 ${t3(d.footingTopEl)}`, 'start'),
- label(X(d.footingFrontM) + 6, Y(d.footingBottomEl) + 4, `기초 하단 ${t3(d.footingBottomEl)}`, 'start'),
+ label(X(d.footingFrontM) + 6, Y(d.footingTopEl) + 4, `상면 ${el(d.footingTopEl)}`, 'start'),
+ label(X(d.footingFrontM) + 6, Y(d.footingBottomEl) + 4, `기초 하단 ${el(d.footingBottomEl)}`, 'start'),
label(X(d.footingBackM), H - 8, '← 배면', 'start', MUTED),
label(X(d.footingFrontM), H - 8, '전면 →', 'end', MUTED),
'',
@@ -118,11 +120,11 @@ export function abutWallElevationSvg(d: AbutWallDrawing): string {
}
const first = d.segments.find((g) => d.lengthM >= g.sFrom && d.lengthM <= g.sTo) ?? d.segments[d.segments.length - 1];
const last = d.segments.find((g) => g.sFrom <= 0 && g.sTo >= 0) ?? d.segments[0];
- parts.push(label(X(d.lengthM), Y(d.parapetTopEndEl) - 6, t3(d.parapetTopEndEl), 'start'));
- parts.push(label(X(0), Y(d.parapetTopStartEl) - 6, t3(d.parapetTopStartEl), 'end'));
- parts.push(label(X(d.lengthM) - 4, Y(first.seatEl) + 4, t3(first.seatEl), 'end'));
- parts.push(label(X(0) + 4, Y(last.seatEl) + 4, t3(last.seatEl), 'start'));
- parts.push(label(X(d.lengthM) - 4, Y(d.footingBottomEl) + 4, t3(d.footingBottomEl), 'end'));
+ parts.push(label(X(d.lengthM), Y(d.parapetTopEndEl) - 6, el(d.parapetTopEndEl), 'start'));
+ parts.push(label(X(0), Y(d.parapetTopStartEl) - 6, el(d.parapetTopStartEl), 'end'));
+ parts.push(label(X(d.lengthM) - 4, Y(first.seatEl) + 4, el(first.seatEl), 'end'));
+ parts.push(label(X(0) + 4, Y(last.seatEl) + 4, el(last.seatEl), 'start'));
+ parts.push(label(X(d.lengthM) - 4, Y(d.footingBottomEl) + 4, el(d.footingBottomEl), 'end'));
parts.push(label(X(d.lengthM), H - 8, 'IP3', 'middle', MUTED));
parts.push(label(X(0), H - 8, 'IP1', 'middle', MUTED));
parts.push(label((X(0) + X(d.lengthM)) / 2, H - 8, `${d.lengthM.toFixed(3)} m`));
diff --git a/src/view/abut-wing-svg.ts b/src/view/abut-wing-svg.ts
index bae4e36..0e2dfc5 100644
--- a/src/view/abut-wing-svg.ts
+++ b/src/view/abut-wing-svg.ts
@@ -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(``);
+ parts.push(``);
}
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(``);
+ parts.push(``);
}
- 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 [
- `