From ee6848c418023fd1cb976033264c1fd824d9157e Mon Sep 17 00:00:00 2001 From: ypshim Date: Fri, 23 Oct 2026 14:02:27 +0900 Subject: [PATCH] =?UTF-8?q?feat(view):=20bearing=20reference=20tables=20on?= =?UTF-8?q?=20=E2=91=A1=20(drawing=20values,=20read-only,=20not=20used=20i?= =?UTF-8?q?n=20the=20model)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The P1 상세도 ELEVATION TABLE and 솔플레이트 TABLE are shown on the bearing card. The model does not use them (Q16). The ELEVATION table compares the drawing's coping top (EL.X − E) with the model's coping top; the difference is a constant 0.211 m on all ten bearings. The solplate table checks T1 and T2 against T ± L·|S|/2 with the 공통 규칙 T = 30 mm; all rows match. Tests check E = h1+h2+h3+h4, L and W, the T1/T2 rule, and the constant offset. Co-Authored-By: Claude Haiku 5.5 --- src/core/bearing-ref.ts | 79 +++++++++++++++++++++++++++++++++++++++ src/main.ts | 52 ++++++++++++++++++++++++++ src/style.css | 4 ++ tests/bearing-ref.test.ts | 35 +++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 src/core/bearing-ref.ts create mode 100644 tests/bearing-ref.test.ts diff --git a/src/core/bearing-ref.ts b/src/core/bearing-ref.ts new file mode 100644 index 0000000..ad4ed44 --- /dev/null +++ b/src/core/bearing-ref.ts @@ -0,0 +1,79 @@ +/** + * 교량받침 reference tables from the P1 상세도 (도면값, 읽기 전용, 2026-10-23). They are not used in the model: + * the model keeps the imported values (Q16). EL.X is the drawing's bearing level; E = h1 + h2 + h3 + h4. + */ +export interface BearingRefRow { + no: number; + row: '전열' | '후열'; + /** EL.X (m). */ + elx: number; + /** 블럭 높이 h1 (mm, 도면). */ + h1: number; + /** 모르타르 h2 (mm). */ + h2: number; + /** 받침 h3 (mm). */ + h3: number; + /** 솔플레이트 h4 (mm). */ + h4: number; + /** E (mm, 도면). */ + E: number; + A: number; + B: number; + C1: number; + C2: number | null; + C3: number | null; + C4: number | null; + C5: number | null; + C6: number | null; + S1: number; +} + +export interface SolplateRefRow { + no: number; + /** 기울기 S (%), negative as drawn. */ + s: number; + L: number; + W: number; + /** 솔플레이트 T (mm). */ + T: number; + T1: number; + T2: number; +} + +const FRONT = { h1: 164, h2: 50, h3: 181, h4: 30, E: 425 }; +const BACK = { h1: 139, h2: 50, h3: 181, h4: 30, E: 400 }; + +export const BEARING_ELEVATION_REF: BearingRefRow[] = [ + { no: 1, row: '전열', elx: 43.625, ...FRONT, A: 700, B: 640, C1: 600, C2: 1298, C3: 2500, C4: 600, C5: 600, C6: 1200, S1: 852 }, + { no: 2, row: '전열', elx: 43.697, ...FRONT, A: 700, B: 640, C1: 600, C2: 1298, C3: 2500, C4: 600, C5: 600, C6: 1200, S1: 852 }, + { no: 3, row: '전열', elx: 43.768, ...FRONT, A: 700, B: 790, C1: 600, C2: 1298, C3: 2500, C4: 600, C5: 600, C6: 1200, S1: 852 }, + { no: 4, row: '전열', elx: 43.840, ...FRONT, A: 700, B: 640, C1: 600, C2: 1298, C3: 2500, C4: 600, C5: 600, C6: 1200, S1: 852 }, + { no: 5, row: '전열', elx: 43.912, ...FRONT, A: 700, B: 640, C1: 600, C2: 1298, C3: 2500, C4: 600, C5: 600, C6: 1200, S1: 852 }, + { no: 6, row: '후열', elx: 43.600, ...BACK, A: 700, B: 540, C1: 602, C2: null, C3: null, C4: null, C5: null, C6: null, S1: 950 }, + { no: 7, row: '후열', elx: 43.672, ...BACK, A: 700, B: 540, C1: 602, C2: null, C3: null, C4: null, C5: null, C6: null, S1: 950 }, + { no: 8, row: '후열', elx: 43.744, ...BACK, A: 700, B: 650, C1: 602, C2: null, C3: null, C4: null, C5: null, C6: null, S1: 950 }, + { no: 9, row: '후열', elx: 43.816, ...BACK, A: 700, B: 540, C1: 602, C2: null, C3: null, C4: null, C5: null, C6: null, S1: 950 }, + { no: 10, row: '후열', elx: 43.887, ...BACK, A: 700, B: 540, C1: 602, C2: null, C3: null, C4: null, C5: null, C6: null, S1: 950 }, +]; + +export const SOLPLATE_REF: SolplateRefRow[] = [ + { no: 1, s: -1.910, L: 800, W: 740, T: 38, T1: 38, T2: 22 }, + { no: 2, s: -1.907, L: 800, W: 740, T: 38, T1: 38, T2: 22 }, + { no: 3, s: -1.905, L: 800, W: 890, T: 38, T1: 38, T2: 22 }, + { no: 4, s: -1.903, L: 800, W: 740, T: 38, T1: 38, T2: 22 }, + { no: 5, s: -1.901, L: 800, W: 740, T: 38, T1: 38, T2: 22 }, + { no: 6, s: -1.910, L: 800, W: 640, T: 38, T1: 38, T2: 22 }, + { no: 7, s: -1.907, L: 800, W: 640, T: 38, T1: 38, T2: 22 }, + { no: 8, s: -1.905, L: 800, W: 750, T: 38, T1: 38, T2: 22 }, + { no: 9, s: -1.903, L: 800, W: 640, T: 38, T1: 38, T2: 22 }, + { no: 10, s: -1.901, L: 800, W: 640, T: 38, T1: 38, T2: 22 }, +]; + +/** 솔플레이트 T of the 공통 규칙 (30 mm). T1 and T2 are computed from this value, not from the table's T column. */ +export const SOLPLATE_T_RULE = 30; + +/** T1 / T2 by the drawing's rule: T ± L·|S|/2, rounded to mm (S in %). */ +export function solplateTs(T: number, L: number, s: number): { T1: number; T2: number } { + const half = (L * Math.abs(s)) / 200; + return { T1: Math.round(T + half), T2: Math.round(T - half) }; +} diff --git a/src/main.ts b/src/main.ts index f3eb15d..030b20d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,6 +16,7 @@ import { buildRebar3D, type RebarColorMode, type RebarScene } from './view/rebar import { DEFAULT_MATERIAL, FCK_MEMBERS, fyOf, GRADE_CHOICES, isAssumedMaterial, materialFrom, type BendFactors, type FckByMember, type FckMember, type GradeKey, type Material } from './core/rebar/common'; import { buildQuantity, quantityRows, type Quantity, type QuantityLine } from './core/rebar/quantity'; import { P1_COPING_GEOM } from './core/coping-geom'; +import { BEARING_ELEVATION_REF, SOLPLATE_REF, SOLPLATE_T_RULE, solplateTs } from './core/bearing-ref'; import { allVisibleChecked, setVisibleChecked, type TypeFilter } from './core/selection'; import { choose, defaultSummary, isSelectable, SHAPE_GROUPS, UNDEFINED_REASON, type MemberId } from './core/shape-options'; import { parseProject, SCHEMA_VERSION, serializeProject, type ProjectFile, type ProjectInfo } from './core/project'; @@ -1246,6 +1247,56 @@ function setMaterial(key: 'fck' | GradeKey, raw: string): void { renderAll(); } +/** ② 교량받침 reference (1단계, 읽기 전용): the drawing's ELEVATION and 솔플레이트 tables, not used in the model (Q16). */ +function renderBearingRef(box: HTMLDivElement, pier: Pier): void { + const m = pier.model; + const wrap = el('div', undefined, 'bearing-ref'); + wrap.appendChild(el('p', '교량받침 ELEVATION TABLE (도면값 · 읽기 전용, mm · EL m)', 'lbl')); + wrap.appendChild(el('p', '도면값은 모델에 쓰지 않습니다(Q16). 아래 "차이"는 도면과 모델의 코핑 상면 차이입니다.', 'muted small')); + const front = m?.segments ?? []; + const backBlocks = m?.blocks.filter((b) => b.row === '후열') ?? []; + const et = el('table', undefined, 'grid-table rebar-table'); + const eh = el('thead'); + const ehr = el('tr'); + for (const h of ['No', '열', 'EL.X', 'E', 'A', 'B', 'C1', 'S1', '도면 상면 (EL.X − E)', '모델 코핑 상면', '차이 (m)']) ehr.appendChild(el('th', h)); + eh.appendChild(ehr); + et.appendChild(eh); + const eb = el('tbody'); + for (const r of BEARING_ELEVATION_REF) { + const drawingTop = r.elx - r.E / 1000; + const modelTop = r.row === '전열' ? front[r.no - 1]?.top : backBlocks[r.no - 6]?.bottom; + const diff = modelTop === undefined ? NaN : modelTop - drawingTop; + eb.appendChild(el('tr')).append( + el('td', String(r.no)), el('td', r.row), el('td', r.elx.toFixed(3)), el('td', String(r.E)), + el('td', String(r.A)), el('td', String(r.B)), el('td', String(r.C1)), el('td', String(r.S1)), + el('td', drawingTop.toFixed(3)), el('td', modelTop === undefined ? '—' : modelTop.toFixed(3)), + el('td', Number.isNaN(diff) ? '—' : diff.toFixed(3)), + ); + } + et.appendChild(eb); + wrap.appendChild(el('div', undefined, 'table-scroll')).appendChild(et); + + wrap.appendChild(el('p', '솔플레이트 TABLE (도면값, mm)', 'lbl')); + const st = el('table', undefined, 'grid-table rebar-table'); + const sh = el('thead'); + const shr = el('tr'); + for (const h of ['No', 'S (%)', 'L', 'W', 'T', 'T1', 'T2', '식 검산']) shr.appendChild(el('th', h)); + sh.appendChild(shr); + st.appendChild(sh); + const sb = el('tbody'); + for (const r of SOLPLATE_REF) { + const calc = solplateTs(SOLPLATE_T_RULE, r.L, r.s); + const ok = calc.T1 === r.T1 && calc.T2 === r.T2; + sb.appendChild(el('tr')).append( + el('td', String(r.no)), el('td', r.s.toFixed(3)), el('td', String(r.L)), el('td', String(r.W)), + el('td', String(r.T)), el('td', String(r.T1)), el('td', String(r.T2)), el('td', ok ? '일치' : `식 ${calc.T1} / ${calc.T2}`), + ); + } + st.appendChild(sb); + wrap.appendChild(el('div', undefined, 'table-scroll')).appendChild(st); + box.appendChild(wrap); +} + /** ② 코핑 상세 (2단계, 읽기 전용): d_IP2 and the 계단 구간 표 from the model (승인 2026-10-23). */ function renderCopingInfo(box: HTMLDivElement, pier: Pier): void { const m = pier.model; @@ -1321,6 +1372,7 @@ function renderPlan(): void { fieldRows(fields, pier, member.keys); if (member.id === 'footing') renderPileRows(fields, pier); if (member.id === 'coping') renderCopingInfo(fields, pier); + if (member.id === 'bearing') renderBearingRef(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; diff --git a/src/style.css b/src/style.css index 36243db..a586984 100644 --- a/src/style.css +++ b/src/style.css @@ -789,3 +789,7 @@ body { background: #eef1f4; font-family: 'Noto Sans KR', system-ui, sans-serif; #viewer-block .origin-wrap { align-self: flex-start; } #viewer-block .viewer-stack-right { position: static; width: auto; } } + +/* ② 교량받침 reference tables (읽기 전용) */ +.bearing-ref .table-scroll { overflow-x: auto; margin-bottom: 8px; } +.bearing-ref table { font-size: 12px; } diff --git a/tests/bearing-ref.test.ts b/tests/bearing-ref.test.ts new file mode 100644 index 0000000..841fcf0 --- /dev/null +++ b/tests/bearing-ref.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from 'vitest'; +import { buildPier } from '../src/core/geometry'; +import { BEARING_ELEVATION_REF, SOLPLATE_REF, SOLPLATE_T_RULE, solplateTs } from '../src/core/bearing-ref'; +import { loadP1 } from './helpers'; + +describe('교량받침 reference (도면값, 읽기 전용, 1단계)', () => { + it('E = h1 + h2 + h3 + h4 on every row', () => { + for (const r of BEARING_ELEVATION_REF) expect(r.h1 + r.h2 + r.h3 + r.h4, `No ${r.no}`).toBe(r.E); + }); + + it('solplate L = A + 100 and W = B + 100 against the elevation row', () => { + BEARING_ELEVATION_REF.forEach((e, i) => { + expect(SOLPLATE_REF[i].L, `No ${e.no}`).toBe(e.A + 100); + expect(SOLPLATE_REF[i].W, `No ${e.no}`).toBe(e.B + 100); + }); + }); + + it('T1 and T2 follow T ± L·|S|/2 on every row (the 검산 column)', () => { + for (const r of SOLPLATE_REF) { + const c = solplateTs(SOLPLATE_T_RULE, r.L, r.s); + expect(c.T1, `No ${r.no}`).toBe(r.T1); + expect(c.T2, `No ${r.no}`).toBe(r.T2); + } + }); + + it('the drawing EL.X − E sits a constant 0.211 m below the model coping top on all ten bearings (Q16 diagnostic)', () => { + const m = buildPier(loadP1()); + const back = m.blocks.filter((b) => b.row === '후열'); + const diffs = BEARING_ELEVATION_REF.map((r) => { + const modelTop = r.row === '전열' ? m.segments[r.no - 1].top : back[r.no - 6].bottom; + return modelTop - (r.elx - r.E / 1000); + }); + for (const d of diffs) expect(d).toBeCloseTo(0.211, 2); + }); +});