feat(view): ② coping card shows d_IP2 and the 계단 구간 표 (read-only, from the model)

The coping card lists d_IP2 and a table of the step segments: range, front and back coping tops, the lateral step, and
the block heights including the 50 mm pad (150 / 200 as in the 삽도). The table is read from the model; nothing new is
computed or entered. Tests check the P1 values against the 삽도.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-23 11:40:09 +09:00
1 parent b5eb0d68c0
commit 5b5db60657
2 files changed
+66

No files matched your search

+35
View File
@@ -1234,6 +1234,40 @@ function setMaterial(key: 'fck' | GradeKey, raw: string): void {
renderAll();
}
/** ② 코핑 상세 (2단계, 읽기 전용): d_IP2 and the 계단 구간 표 from the model (승인 2026-10-23). */
function renderCopingInfo(box: HTMLDivElement, pier: Pier): void {
const m = pier.model;
if (!m) return;
const wrap = el('div', undefined, 'coping-info');
wrap.appendChild(el('p', `d_IP2 (자동) ${m.dIp2.toFixed(3)} m`, 'num'));
wrap.appendChild(el('p', '계단 구간 (BP1 기준 m · EL m · mm)', 'lbl'));
const table = el('table', undefined, 'grid-table rebar-table');
const head = el('thead');
const hr = el('tr');
for (const h of ['구간', '시작', '끝', '전열 상면 EL', '후열 상면 EL', '횡방향 단차 (mm)', '받침블럭 높이 전열 / 후열 (mm)']) hr.appendChild(el('th', h));
head.appendChild(hr);
table.appendChild(head);
const body = el('tbody');
const front = m.blocks.filter((b) => b.row === '전열');
const back = m.blocks.filter((b) => b.row === '후열');
m.segments.forEach((seg, k) => {
const f = front[k];
const b = back[k];
const backTop = b ? b.bottom : seg.top;
const step = Math.round((seg.top - backTop) * 1000);
// Block height includes the 50 mm pad (spec Q16): from the coping top to the pad top.
const heights = f && b ? `${Math.round((f.padTop - f.bottom) * 1000)} / ${Math.round((b.padTop - b.bottom) * 1000)}` : '—';
body.appendChild(el('tr')).append(
el('td', String(k + 1)), el('td', seg.s0.toFixed(3)), el('td', seg.s1.toFixed(3)),
el('td', seg.top.toFixed(3)), el('td', backTop.toFixed(3)), el('td', String(step)), el('td', heights),
);
});
table.appendChild(body);
wrap.appendChild(table);
wrap.appendChild(el('p', '기본 외 형상 규칙(교좌면 모따기·라운드, 2·3주형 헌치)은 spec 2단계 확정 후 추가합니다.', 'muted small'));
box.appendChild(wrap);
}
function renderPlan(): void {
const pier = activePier();
const members = $<HTMLUListElement>('members');
@@ -1274,6 +1308,7 @@ function renderPlan(): void {
} else {
fieldRows(fields, pier, member.keys);
if (member.id === 'footing') renderPileRows(fields, pier);
if (member.id === 'coping') renderCopingInfo(fields, pier);
if (FCK_MEMBERS.includes(member.id as FckMember)) renderMemberFck(fields, member.id as FckMember);
regen.textContent = `다시 생성 · 변경 ${pendingKeys(pier).length}`;
regen.disabled = pendingKeys(pier).length === 0;
+31
View File
@@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest';
import { buildPier } from '../src/core/geometry';
import { loadP1 } from './helpers';
const m = buildPier(loadP1());
const front = m.blocks.filter((b) => b.row === '전열');
const back = m.blocks.filter((b) => b.row === '후열');
describe('② 코핑 계단 구간 표 (2단계 읽기 전용, 시안 삽도)', () => {
it('P1 has five step segments from 0.000 to 12.000 m, as in the 삽도', () => {
expect(m.segments.length).toBe(5);
expect(m.segments[0].s0).toBeCloseTo(0, 6);
expect(m.segments[0].s1).toBeCloseTo(2.196, 3);
expect(m.segments[4].s1).toBeCloseTo(12, 6);
});
it('the front and back coping tops are 43.411 and 43.411 in the first segment, with no lateral step', () => {
expect(m.segments[0].top).toBeCloseTo(43.411, 3);
expect(back[0].bottom).toBeCloseTo(43.411, 3);
expect(Math.round((m.segments[0].top - back[0].bottom) * 1000)).toBe(0);
});
it('block heights including the 50 mm pad are 150 at the front and 200 at the back, as in the 삽도', () => {
expect(Math.round((front[0].padTop - front[0].bottom) * 1000)).toBe(150);
expect(Math.round((back[0].padTop - back[0].bottom) * 1000)).toBe(200);
});
it('d_IP2 is 1.134 m, as in the 삽도', () => {
expect(m.dIp2).toBeCloseTo(1.134, 3);
});
});