diff --git a/src/main.ts b/src/main.ts index 62e2d53..bc9abdd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,6 +20,8 @@ import { groundElFor, parseGroundModel, strataRowsFor, type GroundModelFile } fr import { CORROSION_ALLOWANCE_MM, parseNValue, SEA_DATUM, STRATA_NAMES } from './core/ground'; import { bearingEdgeDistances, integratedCheck, PAIR_ECCENTRICITY_LIMIT_MM, pairEccentricities } from './core/bearing-layout'; import { bearingPlanSvg, type BearingPlanOverlay, PAIR_CHIP_LABELS } from './view/bearing-plan-svg'; +import { bearingSectionSvg, type BearingDrawingRef } from './view/bearing-section-svg'; +import { solplateDetailSvg, type SolplateDrawingRef } from './view/solplate-svg'; import { groundSectionSvg } from './view/ground-svg'; import { parseBearingElevationTable, parseSolplateTable, readDxfTexts } from './core/dxf-table'; import { columnDiameterIssues } from './core/column-check'; @@ -1520,18 +1522,30 @@ function renderBearingBasis(wrap: HTMLDivElement, pier: Pier): void { let bearingPairIndex = 0; let bearingOverlayOn = true; -/** Drawing A, B of the front and back bearings of a pair: the imported drawing rows if any, else the reference table. */ +/** + * Drawing values of one bearing for the overlays (승인 2026-10-26): the imported drawing row where it has the value + * (elx, h1, h2, A, B), otherwise the reference table (h3, h4, E). + */ +function drawingRefFor(pier: Pier, no: number): BearingDrawingRef | undefined { + const r = BEARING_ELEVATION_REF.find((x) => x.no === no); + if (!r) return undefined; + const d = (bearingDrawing[pier.input.name] ?? []).find((x) => x.no === no); + const A = d?.A ?? r.A; + const B = d?.B ?? r.B; + return { elx: d?.elx ?? r.elx, h1: d?.h1 ?? r.h1, h2: d?.h2 ?? r.h2, h3: r.h3, h4: r.h4, E: r.E, A, B }; +} + +/** Drawing A, B of the front and back bearings of a pair, for the plan overlay. */ function overlayFor(pier: Pier, frontNo: number, backNo: number): BearingPlanOverlay | undefined { - const rows = bearingDrawing[pier.input.name] ?? []; - const pick = (no: number) => { - const d = rows.find((r) => r.no === no); - if (d?.A !== undefined && d?.B !== undefined) return { A: d.A, B: d.B }; - const r = BEARING_ELEVATION_REF.find((x) => x.no === no); - return r ? { A: r.A, B: r.B } : undefined; - }; - const f = pick(frontNo); - const b = pick(backNo); - return f && b ? { front: f, back: b } : undefined; + const f = drawingRefFor(pier, frontNo); + const b = drawingRefFor(pier, backNo); + return f && b ? { front: { A: f.A, B: f.B }, back: { A: b.A, B: b.B } } : undefined; +} + +/** Drawing solplate values of one bearing (reference table), for the solplate detail overlay. */ +function solplateRefFor(no: number): SolplateDrawingRef | undefined { + const r = SOLPLATE_REF.find((x) => x.no === no); + return r ? { L: r.L, W: r.W, T1: r.T1, T2: r.T2, S: r.s } : undefined; } function renderBearingLayout(wrap: HTMLDivElement, pier: Pier): void { @@ -1568,8 +1582,26 @@ function renderBearingLayout(wrap: HTMLDivElement, pier: Pier): void { const fig = el('figure', undefined, 'ground-fig'); const overlay = bearingOverlayOn ? overlayFor(pier, current.frontNo, current.backNo) : undefined; fig.innerHTML = bearingPlanSvg(pier.model, bearingPairIndex, overlay); - fig.appendChild(el('figcaption', '평면 · 실선 = 모델(솔플레이트, 블록 윤곽) · 점선 = 도면값 · C4·C5는 교각축 직각 방향 코핑 가장자리에서 받침까지', 'muted small')); + fig.appendChild(el('figcaption', '평면 · 실선 = 모델(솔플레이트, 블록 윤곽) · 점선 = 도면값 · C4·C5는 교축 방향, 받침 중심에서 블록 바깥 연단까지', 'muted small')); wrap.appendChild(fig); + + // 단면 (elevation): the blocks, mortar, bearing body and solplates; the drawing heights dashed. + const section = el('figure', undefined, 'ground-fig'); + const refs = bearingOverlayOn ? { front: drawingRefFor(pier, current.frontNo), back: drawingRefFor(pier, current.backNo) } : undefined; + section.innerHTML = bearingSectionSvg(pier.model, bearingPairIndex, refs); + section.appendChild(el('figcaption', '단면 · EL.X는 모르타르 상단, E = h1 + h2 + h3 + h4 · 점선 = 도면 높이', 'muted small')); + wrap.appendChild(section); + + // 솔플레이트 상세 for the two bearings of the pair (plan and taper, drawing dashed). + const solGrid = el('div', undefined, 'footing-grid'); + for (const no of [current.frontNo, current.backNo]) { + const block = pier.model.blocks.find((b) => b.no === no)!; + const fig2 = el('figure', undefined, 'footing-fig'); + fig2.innerHTML = solplateDetailSvg(block, bearingOverlayOn ? solplateRefFor(no) : undefined); + fig2.appendChild(el('figcaption', `솔플레이트 상세 · ${no}번 · 두께 과장 · 점선 = 도면값`, 'muted small')); + solGrid.appendChild(fig2); + } + wrap.appendChild(solGrid); } wrap.appendChild(el('p', `배치 규칙 (모델값 · 편심 한계 ${PAIR_ECCENTRICITY_LIMIT_MM} mm)`, 'lbl')); const edges = bearingEdgeDistances(pier.model); diff --git a/src/view/bearing-section-svg.ts b/src/view/bearing-section-svg.ts new file mode 100644 index 0000000..d416608 --- /dev/null +++ b/src/view/bearing-section-svg.ts @@ -0,0 +1,82 @@ +import type { PierModel } from '../core/geometry'; +import { pairEccentricities } from '../core/bearing-layout'; + +/** + * Drawing values of one bearing (도면값), overlaid as dashed outlines (승인 2026-10-26). + * All lengths in mm; elx in m. + */ +export interface BearingDrawingRef { + elx?: number; + h1: number; + h2: number; + h3: number; + h4: number; + E: number; + A: number; + B: number; +} + +/** + * 교량받침 상세 · 단면 (elevation) for one pair (승인 2026-10-26): the model's blocks, mortar, bearing body, + * and solplate per bearing, with the drawing heights overlaid as dashed outlines. x is the 교축 (n), y the EL (up). + * Labels: EL.X (mortar top), h1 (block), h2 (mortar), h3 (bearing), h4 (solplate), E = h1 + h2 + h3 + h4. + */ +export function bearingSectionSvg(m: PierModel, pairIndex: number, refs?: { front?: BearingDrawingRef; back?: BearingDrawingRef }): string { + const pair = pairEccentricities(m.blocks)[pairIndex]; + if (!pair) return ''; + const f = m.blocks.find((b) => b.no === pair.frontNo && b.row === '전열')!; + const k = m.blocks.find((b) => b.no === pair.backNo && b.row === '후열')!; + const nc = (f.n + k.n) / 2; + const elc = (f.bottom + k.bottom) / 2; + const X = (n: number) => (n - nc) * 1000; + const Y = (el: number) => -(el - elc) * 1000; + const parts: string[] = []; + + // Blocks: one stepped outline for 일체, else one per bearing. + const ib = m.integratedBlocks.find((x) => x.frontNo === pair.frontNo); + if (ib) { + const x0 = X(ib.n - 1.25); + const x1 = X(ib.n + 1.25); + const xm = X(ib.n); + parts.push(``); + } else { + for (const b of [f, k]) { + parts.push(``); + } + } + + // Mortar, bearing body and solplate per bearing, from the model. + for (const [b, ref] of [[f, refs?.front], [k, refs?.back]] as const) { + const pad = 1000; + parts.push(``); + if (b.plan) { + const { A, margin, h3, T } = b.plan; + const L = A + margin; + const bodyTop = b.padTop + h3 / 1000; + parts.push(``); + const drop = (L * Math.abs(b.plan.S)) / 200; + const t1 = T + drop; + const t2 = T - drop; + const x0 = X(b.n) - L / 2; + const x1 = X(b.n) + L / 2; + parts.push(``); + } + if (ref) { + // Drawing heights from the block bottom, dashed (overlay). + const base = b.bottom; + const dBlockTop = base + (ref.h1 ?? 0) / 1000; + const dPad = dBlockTop + ref.h2 / 1000; + const dBody = dPad + ref.h3 / 1000; + const dSol = dBody + ref.h4 / 1000; + parts.push(``); + parts.push(``); + parts.push(``); + } + // EL.X at the mortar top, bearing number. + parts.push(``); + parts.push(`${b.no} · EL.X ${b.padTop.toFixed(3)}`); + parts.push(`h1 ${Math.round((b.top - b.bottom) * 1000)}`); + } + return `${parts.join('')}`; +} + diff --git a/src/view/solplate-svg.ts b/src/view/solplate-svg.ts new file mode 100644 index 0000000..bdd8823 --- /dev/null +++ b/src/view/solplate-svg.ts @@ -0,0 +1,47 @@ +import type { BlockModel } from '../core/geometry'; + +/** Drawing values of one solplate (도면값), overlaid as dashed outlines. mm. */ +export interface SolplateDrawingRef { + L: number; + W: number; + T1: number; + T2: number; + S: number; +} + +/** + * 솔플레이트 상세 (승인 2026-10-26) for one bearing: the plan (L across the 교축, W along the 교각축) and the + * taper in side view (T1 at the (−) side, T2 at the (+) side, S % · T). Model solid, drawing dashed. + */ +export function solplateDetailSvg(block: BlockModel, ref?: SolplateDrawingRef): string { + const plan = block.plan; + if (!plan) return ''; + const L = plan.A + plan.margin; + const W = plan.B + plan.margin; + const drop = (L * Math.abs(plan.S)) / 200; + const t1 = plan.T + drop; + const t2 = plan.T - drop; + const parts: string[] = []; + // Plan (left): L across, W along. + parts.push(``); + parts.push(`L ${L.toFixed(0)}`); + parts.push(`W ${W.toFixed(0)}`); + if (ref) { + parts.push(``); + } + // Side view (right): thickness T1 at x = −L/2 and T2 at x = +L/2, shown with the taper. + const ox = L / 2 + 500; + const sc = 4; // exaggerated thickness for legibility, labelled + parts.push(``); + parts.push(`T1 ${t1.toFixed(0)}`); + parts.push(`T2 ${t2.toFixed(0)}`); + parts.push(`S ${plan.S} % · T ${plan.T} (두께 과장 ×${sc})`); + if (ref) { + const r1 = ref.T1; + const r2 = ref.T2; + parts.push(``); + } + const minX = -L / 2 - 200; + const maxX = ox + L / 2 + 200; + return `${parts.join('')}`; +} diff --git a/tests/bearing-section-svg.test.ts b/tests/bearing-section-svg.test.ts new file mode 100644 index 0000000..06e2dc0 --- /dev/null +++ b/tests/bearing-section-svg.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest'; +import { buildPier } from '../src/core/geometry'; +import { bearingSectionSvg } from '../src/view/bearing-section-svg'; +import { solplateDetailSvg } from '../src/view/solplate-svg'; +import { loadP1 } from './helpers'; + +const model = buildPier(loadP1()); +const integrated = buildPier({ ...loadP1(), bearingLayout: '일체' }); +const ref = { h1: 150, h2: 50, h3: 181, h4: 30, E: 425, A: 700, B: 640 }; + +describe('교량받침 상세 단면 (승인 2026-10-26)', () => { + it('labels EL.X at the mortar top and the block height h1 for each bearing', () => { + const svg = bearingSectionSvg(model, 0); + expect(svg).toContain('EL.X'); + expect(svg).toContain('h1 150'); + }); + + it('draws the drawing heights dashed only when given', () => { + expect(bearingSectionSvg(model, 0)).not.toContain('stroke-dasharray="40 25"'); + const svg = bearingSectionSvg(model, 0, { front: ref, back: ref }); + expect((svg.match(/stroke-dasharray="40 25"/g) ?? []).length).toBeGreaterThan(0); + }); + + it('일체 draws one stepped outline (a polygon) for the pair', () => { + expect(bearingSectionSvg(integrated, 0)).toContain(' { + expect(bearingSectionSvg(model, 99)).toBe(''); + }); +}); + +describe('솔플레이트 상세 (승인 2026-10-26)', () => { + it('shows L, W, T1 and T2 of the model plan, with the drawing dashed when given', () => { + const withPlan = buildPier({ ...loadP1(), bearingPlans: [{ no: 1, A: 700, B: 640, S: -1.9 }] }); + const block = withPlan.blocks.find((b) => b.no === 1)!; + const svg = solplateDetailSvg(block); + expect(svg).toContain('L '); + expect(svg).toContain('T1 '); + expect(svg).toContain('T2 '); + const withRef = solplateDetailSvg(block, { L: 800, W: 748, T1: 38, T2: 22, S: 1.9 }); + expect(withRef).toContain('stroke-dasharray="40 25"'); + }); +});