refactor(abut): 벽체·흉벽 카드의 삽도 삭제

- 형상 카드의 삽도 SVG 표시를 제거하고, 삽도 모듈(src/view/abut-section-svg.ts)과 테스트를 삭제
- 입력 격자, 구간 표, 확인 필요 노트는 그대로 둠
- 테스트 637건 통과, 타입 검사·빌드 통과. 브라우저 시각 확인은 미실시

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-03 18:05:32 +09:00
1 parent 842d372312
commit e1f85e5d86
4 files changed
+2 -103

No files matched your search

-9
View File
@@ -12,7 +12,6 @@ import { applyAbutEdit, applyBearingEdit, applyShapeEdit, ABUT_EDIT_FIELDS, SHAP
import { ABUT_SHAPE_DEFAULT, resolveShape } from './core/abut/shape';
import { abutShown, piersShown } from './core/abut/visibility';
import { abutMemberRows, ABUT_MEMBER_EDITABLE, type AbutMemberId } from './core/abut/member-list';
import { abutSectionSvg } from './view/abut-section-svg';
import type { WingForm } from './core/abut/geometry';
import { buildMembers } from './core/abut/members';
import { buildWings } from './core/abut/wings';
@@ -791,14 +790,6 @@ function fillAbutSlot(slot: (typeof ABUT_SLOTS)[keyof typeof ABUT_SLOTS], rec: A
wingBox.replaceChildren();
bearings.replaceChildren();
shape.replaceChildren();
// 삽도 (벽체·흉벽 개략 단면): the values it names are the card's values, so the drawing follows each change.
const drawing = el('div', undefined, 'abut-section');
drawing.innerHTML = abutSectionSvg(
resolveShape(rec.shape),
{ ...ABUT_SHAPE_DEFAULT, ...(rec.shape ?? {}) },
{ seatEl: rec.seatEl, footingBottomEl: rec.foundationBottomEl, groundEl: rec.groundEl },
);
shape.append(drawing);
msg.textContent = '';
const apply = (next: AbutRecord) => {
state.abutments = state.abutments.map((a) => (a.name === next.name ? next : a));
+2 -9
View File
@@ -869,13 +869,6 @@ body { background: #eef1f4; font-family: 'Noto Sans KR', system-ui, sans-serif;
.quantity-total { margin-bottom: 16px; }
/* 교대 삽도 (벽체·흉벽 개략 단면): fills the panel width; the labels follow the card's values. */
.abut-section { margin-bottom: 12px; background: #f6f8fa; border: 1px solid #d5dbe2; border-radius: 6px; padding: 8px; }
.abut-section svg { display: block; width: 100%; height: auto; }
.abut-section text { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 12px; fill: #18212b; }
.abut-section rect { stroke: #55606e; stroke-width: 1; }
.abut-section .abut-footing { fill: #b9c3ce; }
.abut-section .abut-wall { fill: #d8dee6; }
.abut-section .abut-parapet { fill: #9aa7b5; }
/* ② 형상계획: with an abutment selected, only the abutment card shows; the pier cards are hidden (2026-11-03). */
#view-plan.abut-selected .members-title,
@@ -890,9 +883,9 @@ body { background: #eef1f4; font-family: 'Noto Sans KR', system-ui, sans-serif;
#view-plan.abut-selected #abut-members { display: block; }
#abut-members .pier-name span:first-child { display: block; }
/* 교대 형상 입력: the 삽도 on top, then a 3-column grid of input · default (시안 ②), the 확인 필요 notes at the end (2026-11-03). */
/* 교대 형상 입력: a 3-column grid of input · default (시안 ②), the 확인 필요 notes at the end (2026-11-03). */
#abut-panel-plan-shape, #abut-panel-shape { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 1fr); gap: 6px 10px; align-items: center; }
#abut-panel-plan-shape > .abut-section, #abut-panel-shape > .abut-section, #abut-panel-plan-shape > .abut-qa { grid-column: 1 / -1; }
#abut-panel-plan-shape > .abut-qa { grid-column: 1 / -1; }
.abut-qa { border-top: 1px solid #d5dbe2; padding-top: 8px; }
.abut-qa p { margin: 3px 0; }
-51
View File
@@ -1,51 +0,0 @@
import { type AbutShape, type ResolvedShape } from '../core/abut/shape';
/** EL values (m) the drawing names as read-only labels; they come from the record, not from this card. */
export interface AbutLevels {
seatEl: number;
footingBottomEl: number;
groundEl: number;
}
/**
* 벽체·흉벽 개략 단면 (시안 ② 삽도). Not to scale: the heights are fixed for legibility,
* and the labels show the card's values in mm, so the numbers the user types are what the drawing names.
* t runs along the 교대 직각 방향 (배면 −, 전면 +); the 교대축 is t = 0.
*/
export function abutSectionSvg(r: ResolvedShape, v: AbutShape, levels?: AbutLevels): string {
const S = 100; // px per m
const YMAX = 4.6; // highest drawn height (m above the footing bottom)
const tMin = r.footingBackM - 0.4;
const tMax = r.footingFrontM + 0.6;
const W = (tMax - tMin) * S;
const H = (YMAX + 0.8) * S;
const X = (t: number) => (t - tMin) * S;
const Y = (h: number) => (YMAX - h) * S;
const fT = r.footingThicknessM;
const wallTop = fT + 2.0; // illustrative wall height
const parapetTop = wallTop + 0.9; // illustrative parapet height
const rect = (t0: number, t1: number, h0: number, h1: number, cls: string) =>
`<rect class="${cls}" x="${X(t0)}" y="${Y(h1)}" width="${(t1 - t0) * S}" height="${(h1 - h0) * S}"/>`;
const text = (x: number, y: number, s: string, anchor = 'start') =>
`<text x="${x}" y="${y}" text-anchor="${anchor}">${s}</text>`;
const wallMid = (r.wallBackM + r.wallFrontM) / 2;
return [
`<svg viewBox="0 0 ${W} ${H}" width="100%" role="img" aria-label="벽체·흉벽 개략 단면 (축척 아님)">`,
rect(r.footingBackM, r.footingFrontM, 0, fT, 'abut-footing'),
rect(r.wallBackM, r.wallFrontM, fT, wallTop, 'abut-wall'),
rect(r.parapetBackM, 0, wallTop, parapetTop, 'abut-parapet'),
text(4, Y(wallTop + 0.45), `흉벽 높이 ${v.parapetHeightMm}`),
text(X(r.parapetBackM) - 4, Y(parapetTop + 0.2), `흉벽 두께 ${v.parapetThicknessMm}`, 'start'),
text(X(wallMid), Y(fT + 1.0), `벽체 두께 ${v.wallThicknessMm}`, 'middle'),
text(X(r.wallFrontM) + 6, Y(wallTop - 0.3), `벽체 전면 ${v.wallFrontMm}`),
text(X(r.wallFrontM) + 6, Y(wallTop - 0.8), `브라켓 높이 ${v.bracketHeightMm}`),
text(X(r.wallFrontM) + 6, Y(fT + 0.4), `수축줄눈 ${v.shrinkJointCount}개`),
text(4, Y(fT / 2) + 4, `저판 두께 ${v.footingThicknessMm}`),
text(X(r.footingBackM), Y(0) + 18, `저판 뒷굽 ${v.footingBackMm}`),
text(X(r.footingFrontM), Y(0) + 18, `저판 앞굽 ${v.footingFrontMm}`, 'end'),
levels ? text(4, H - 44, `교좌면 ${levels.seatEl.toFixed(3)} · 기초 하단 ${levels.footingBottomEl.toFixed(3)} · 지반고 ${levels.groundEl.toFixed(3)} m`, 'start') : '',
text(4, H - 22, '개략 도시 · 축척 아님', 'start'),
text(4, H - 6, `흉벽 상단 Level 좌 ${v.parapetLevelLeftMm} · 우 ${v.parapetLevelRightMm} (계산 미사용, QA-4)`, 'start'),
'</svg>',
].join('');
}
-34
View File
@@ -1,34 +0,0 @@
import { describe, expect, it } from 'vitest';
import { ABUT_SHAPE_DEFAULT, resolveShape } from '../src/core/abut/shape';
import { abutSectionSvg } from '../src/view/abut-section-svg';
describe('교대 삽도 (벽체·흉벽 개략 단면, 시안 ②)', () => {
it('names the card values in mm, including the new ones', () => {
const svg = abutSectionSvg(resolveShape(), ABUT_SHAPE_DEFAULT);
expect(svg).toContain('흉벽 높이 2500');
expect(svg).toContain('벽체 두께 2400');
expect(svg).toContain('저판 뒷굽 -1800');
expect(svg).toContain('수축줄눈 1개');
expect(svg).toContain('Level 좌 450 · 우 995');
});
it('follows a changed value, and says it is not to scale', () => {
const v = { ...ABUT_SHAPE_DEFAULT, shrinkJointCount: 3, parapetHeightMm: 2200 };
const svg = abutSectionSvg(resolveShape(v), v);
expect(svg).toContain('수축줄눈 3개');
expect(svg).toContain('흉벽 높이 2200');
expect(svg).toContain('축척 아님');
});
it('names the 교좌면, 기초 하단 and 지반고 EL as read-only labels when given', () => {
const svg = abutSectionSvg(resolveShape(), ABUT_SHAPE_DEFAULT, { seatEl: 44.367, footingBottomEl: 40.895, groundEl: 33.984 });
expect(svg).toContain('교좌면 44.367 · 기초 하단 40.895 · 지반고 33.984 m');
});
it('draws the footing, wall and parapet as separate shapes', () => {
const svg = abutSectionSvg(resolveShape(), ABUT_SHAPE_DEFAULT);
expect(svg.match(/<rect /g)).toHaveLength(3);
expect(svg.startsWith('<svg')).toBe(true);
expect(svg.endsWith('</svg>')).toBe(true);
});
});