feat(bearing): 솔플레이트 표에 '방위각 검사 (″)' 열 (Q-SH4)
교각·교대 솔플레이트 상세 TABLE에 받침별로 엑셀 방위각과 짝 받침 방향의 차이(초)를 보인다. 10″ 이하 "≤ 10″", 넘으면 WS01, 짝이 없으면 "— (짝 없음)". - BearingSole.azimuthCheckSec (짝의 from/to 쪽에 맞는 값), 표 행 azimuthSec - 테스트: P1 전열 ≤ 0.5″ · 후열 없음, A1 ≤ 0.5″, P1 ③ 방위각 75°59′ 오타 → 약 87″ · WS01 그 받침만 - 브라우저 확인(운곡교 완도·광주 데이터): P1-광주 전열 짝 A1-광주 0.0–0.3″, 후열 짝 P2-광주 0.0–0.1″ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
54c855ab1a
commit
3bd2340609
7 files changed
+41
-8
No files matched your search
@@ -129,6 +129,8 @@ export interface AbutSolplateRow {
|
|||||||
/** S 근거 (계산 / 입력 / 가정 / 수정됨 / 없음) and the 짝 받침, e.g. "P1-①". */
|
/** S 근거 (계산 / 입력 / 가정 / 수정됨 / 없음) and the 짝 받침, e.g. "P1-①". */
|
||||||
source: string;
|
source: string;
|
||||||
pair?: string;
|
pair?: string;
|
||||||
|
/** 방위각 검사 (″): input azimuth against the pair direction; absent without a pair. */
|
||||||
|
azimuthSec?: number;
|
||||||
L: number;
|
L: number;
|
||||||
W: number;
|
W: number;
|
||||||
T: number;
|
T: number;
|
||||||
@@ -143,7 +145,7 @@ export function abutSolplateRows(model: AbutModel, rule: AbutBearingRule, height
|
|||||||
return abutSoles(model, rule, heightMm, ctx).map((p) => {
|
return abutSoles(model, rule, heightMm, ctx).map((p) => {
|
||||||
const rhs = (p.L * Math.abs(p.slopePct)) / 100;
|
const rhs = (p.L * Math.abs(p.slopePct)) / 100;
|
||||||
return {
|
return {
|
||||||
no: p.no, S: p.slopePct, source: p.slopeSource,
|
no: p.no, S: p.slopePct, source: p.slopeSource, azimuthSec: p.azimuthCheckSec,
|
||||||
pair: p.pair ? `${p.pair.structure}-${CIRCLED[p.pair.no - 1] ?? p.pair.no}` : undefined,
|
pair: p.pair ? `${p.pair.structure}-${CIRCLED[p.pair.no - 1] ?? p.pair.no}` : undefined,
|
||||||
L: p.L, W: p.W, T: p.T, T1: p.T1, T2: p.T2,
|
L: p.L, W: p.W, T: p.T, T1: p.T1, T2: p.T2,
|
||||||
check: `${p.T1 - p.T2} ≈ ${rhs.toFixed(1)} ${Math.abs(p.T1 - p.T2 - rhs) <= 1 + 1e-9 ? '일치' : '불일치 (W08)'}`,
|
check: `${p.T1 - p.T2} ≈ ${rhs.toFixed(1)} ${Math.abs(p.T1 - p.T2 - rhs) <= 1 + 1e-9 ? '일치' : '불일치 (W08)'}`,
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ export interface SolplateRow {
|
|||||||
ok: boolean;
|
ok: boolean;
|
||||||
/** 짝 받침 (같은 거더), e.g. "A1-①"; absent when none (2026-11-06). */
|
/** 짝 받침 (같은 거더), e.g. "A1-①"; absent when none (2026-11-06). */
|
||||||
pair?: string;
|
pair?: string;
|
||||||
|
/** 방위각 검사 (″): input azimuth against the pair direction; absent without a pair. */
|
||||||
|
azimuthSec?: number;
|
||||||
/** Where S comes from: 계산 (짝) / 입력 (종단경사) / 가정 (다른 열) / 수정됨 / 없음. */
|
/** Where S comes from: 계산 (짝) / 입력 (종단경사) / 가정 (다른 열) / 수정됨 / 없음. */
|
||||||
source: SlopeSource;
|
source: SlopeSource;
|
||||||
/** T typed by the user (수정됨) rather than automatic. */
|
/** T typed by the user (수정됨) rather than automatic. */
|
||||||
@@ -130,7 +132,7 @@ export function solplateRows(m: PierModel): SolplateRow[] {
|
|||||||
const checkRhs = (p.L * Math.abs(p.slopePct)) / 100;
|
const checkRhs = (p.L * Math.abs(p.slopePct)) / 100;
|
||||||
return {
|
return {
|
||||||
pair: s.pair ? `${s.pair.structure}-${CIRCLED_NO[s.pair.no - 1] ?? s.pair.no}` : undefined,
|
pair: s.pair ? `${s.pair.structure}-${CIRCLED_NO[s.pair.no - 1] ?? s.pair.no}` : undefined,
|
||||||
source: s.slopeSource, tTyped: s.tTyped, A: s.aMm, B: s.bMm, warnings: s.warnings,
|
azimuthSec: s.azimuthCheckSec, source: s.slopeSource, tTyped: s.tTyped, A: s.aMm, B: s.bMm, warnings: s.warnings,
|
||||||
no: b.no,
|
no: b.no,
|
||||||
S: p.slopePct,
|
S: p.slopePct,
|
||||||
L: p.L,
|
L: p.L,
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ export interface BearingSole {
|
|||||||
no: number;
|
no: number;
|
||||||
row: string;
|
row: string;
|
||||||
pair?: { structure: string; no: number };
|
pair?: { structure: string; no: number };
|
||||||
|
/** 방위각 검사 (″): this bearing's input azimuth against the direction to its pair (Q-SH4, WS01 > 10″). Absent without a pair. */
|
||||||
|
azimuthCheckSec?: number;
|
||||||
slopePct: number;
|
slopePct: number;
|
||||||
slopeSource: SlopeSource;
|
slopeSource: SlopeSource;
|
||||||
aMm: number;
|
aMm: number;
|
||||||
@@ -116,6 +118,7 @@ export function structureSoles(structure: string, bearings: SoleBearing[], setti
|
|||||||
const sign = Math.sign(slopePct);
|
const sign = Math.sign(slopePct);
|
||||||
return {
|
return {
|
||||||
no: b.no, row: b.row, pair: pair ? (pair.from.structure === structure ? pair.to : pair.from) : undefined,
|
no: b.no, row: b.row, pair: pair ? (pair.from.structure === structure ? pair.to : pair.from) : undefined,
|
||||||
|
azimuthCheckSec: pair ? (pair.from.structure === structure && pair.from.no === b.no ? pair.fromCheckSec : pair.toCheckSec) : undefined,
|
||||||
slopePct, slopeSource: source, aMm, bMm, L, W, T, tTyped: typed, T1, T2, tReq: req[i], h3: s.h3Mm,
|
slopePct, slopeSource: source, aMm, bMm, L, W, T, tTyped: typed, T1, T2, tReq: req[i], h3: s.h3Mm,
|
||||||
dropMm: T - s.tRefMm, uphill: sign ? { e: sign * dir.e, n: sign * dir.n } : null, warnings,
|
dropMm: T - s.tRefMm, uphill: sign ? { e: sign * dir.e, n: sign * dir.n } : null, warnings,
|
||||||
};
|
};
|
||||||
|
|||||||
+11
-5
@@ -120,7 +120,7 @@ import { footingPlanSvg, footingSideSvg } from './view/footing-svg';
|
|||||||
import { P1_COPING_GEOM } from './core/coping-geom';
|
import { P1_COPING_GEOM } from './core/coping-geom';
|
||||||
import { SOLE_DEFAULTS, type SoleSettings, soleSettingsIssues } from './core/sole-plate';
|
import { SOLE_DEFAULTS, type SoleSettings, soleSettingsIssues } from './core/sole-plate';
|
||||||
import { bridgePairs } from './core/bridge-pairs';
|
import { bridgePairs } from './core/bridge-pairs';
|
||||||
import type { BearingPair } from './core/bearing-pair';
|
import { AZIMUTH_CHECK_SEC, type BearingPair } from './core/bearing-pair';
|
||||||
import { type BarSummary, elevationRows, solplateRows } from './core/bearing-table';
|
import { type BarSummary, elevationRows, solplateRows } from './core/bearing-table';
|
||||||
|
|
||||||
/** 지반 모델 (ground_model.json) for the project. Its values reach a pier's 지반고 only when the user applies them. */
|
/** 지반 모델 (ground_model.json) for the project. Its values reach a pier's 지반고 only when the user applies them. */
|
||||||
@@ -1018,8 +1018,8 @@ function fillAbutBearingCard(
|
|||||||
box.append(el('p', '솔플레이트 상세 TABLE (mm) · S는 같은 거더 양끝 받침에서 계산', 'lbl'));
|
box.append(el('p', '솔플레이트 상세 TABLE (mm) · S는 같은 거더 양끝 받침에서 계산', 'lbl'));
|
||||||
box.append(
|
box.append(
|
||||||
table(
|
table(
|
||||||
['No.', '짝 받침', 'S (%)', 'S 근거', 'L', 'W', 'T', 'T1 (오르막 쪽)', 'T2', '검산 T1 − T2 ≈ L·|S|/100', '검사'],
|
['No.', '짝 받침', '방위각 검사 (″)', 'S (%)', 'S 근거', 'L', 'W', 'T', 'T1 (오르막 쪽)', 'T2', '검산 T1 − T2 ≈ L·|S|/100', '검사'],
|
||||||
sol.map((r) => [String(r.no), r.pair ?? '없음', r.S.toFixed(3), r.source, String(r.L), String(r.W), String(r.T), String(r.T1), String(r.T2), r.check, r.warnings.length ? r.warnings.join(' · ') : '—']),
|
sol.map((r) => [String(r.no), r.pair ?? '없음', azimuthCheckText(r.azimuthSec), r.S.toFixed(3), r.source, String(r.L), String(r.W), String(r.T), String(r.T1), String(r.T2), r.check, r.warnings.length ? r.warnings.join(' · ') : '—']),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
// 솔플레이트 입력 (결정 2026-11-06): 교각 카드와 같은 항목, 받침이 1열이라 T 하나.
|
// 솔플레이트 입력 (결정 2026-11-06): 교각 카드와 같은 항목, 받침이 1열이라 T 하나.
|
||||||
@@ -5139,7 +5139,7 @@ function renderSolplateTable(wrap: HTMLDivElement, pier: Pier): void {
|
|||||||
const table = el('table', undefined, 'grid-table rebar-table');
|
const table = el('table', undefined, 'grid-table rebar-table');
|
||||||
const head = el('thead');
|
const head = el('thead');
|
||||||
const hr = el('tr');
|
const hr = el('tr');
|
||||||
for (const h of ['No', '짝 받침', 'S (%)', 'S 근거', 'A', 'B', 'L', 'W', 'T', 'T1 (오르막 쪽)', 'T2', '검산 T1 − T2 ≈ L·|S|/100', '검사']) hr.appendChild(el('th', h));
|
for (const h of ['No', '짝 받침', '방위각 검사 (″)', 'S (%)', 'S 근거', 'A', 'B', 'L', 'W', 'T', 'T1 (오르막 쪽)', 'T2', '검산 T1 − T2 ≈ L·|S|/100', '검사']) hr.appendChild(el('th', h));
|
||||||
head.appendChild(hr);
|
head.appendChild(hr);
|
||||||
table.appendChild(head);
|
table.appendChild(head);
|
||||||
const body = el('tbody');
|
const body = el('tbody');
|
||||||
@@ -5155,7 +5155,7 @@ function renderSolplateTable(wrap: HTMLDivElement, pier: Pier): void {
|
|||||||
for (const r of solplateRows(pier.model)) {
|
for (const r of solplateRows(pier.model)) {
|
||||||
const key = String(r.no);
|
const key = String(r.no);
|
||||||
const tr = el('tr');
|
const tr = el('tr');
|
||||||
tr.append(el('td', String(r.no)), el('td', r.pair ?? '없음'));
|
tr.append(el('td', String(r.no)), el('td', r.pair ?? '없음'), el('td', azimuthCheckText(r.azimuthSec)));
|
||||||
tr.appendChild(cellInput(Math.round(r.S * 1000) / 1000, r.source === '수정됨', `받침 ${r.no} S (%)`, (v) => {
|
tr.appendChild(cellInput(Math.round(r.S * 1000) / 1000, r.source === '수정됨', `받침 ${r.no} S (%)`, (v) => {
|
||||||
const next = { ...(settings.slopePct ?? {}) };
|
const next = { ...(settings.slopePct ?? {}) };
|
||||||
if (v === undefined) delete next[key]; else next[key] = v;
|
if (v === undefined) delete next[key]; else next[key] = v;
|
||||||
@@ -5180,6 +5180,12 @@ function renderSolplateTable(wrap: HTMLDivElement, pier: Pier): void {
|
|||||||
wrap.appendChild(el('div', undefined, 'table-scroll')).appendChild(table);
|
wrap.appendChild(el('div', undefined, 'table-scroll')).appendChild(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 방위각 검사 칸 (Q-SH4): the difference to the pair direction in arc seconds, ≤ 10″ 통과 / > 10″ WS01; '—' without a pair. */
|
||||||
|
function azimuthCheckText(sec: number | undefined): string {
|
||||||
|
if (sec === undefined) return '— (짝 없음)';
|
||||||
|
return `${sec.toFixed(1)}″ ${sec > AZIMUTH_CHECK_SEC ? 'WS01' : '≤ 10″'}`;
|
||||||
|
}
|
||||||
|
|
||||||
/** Sets one pier's solplate settings (2026-11-06) and rebuilds that pier. */
|
/** Sets one pier's solplate settings (2026-11-06) and rebuilds that pier. */
|
||||||
function setSole(name: string, sole: SoleSettings): void {
|
function setSole(name: string, sole: SoleSettings): void {
|
||||||
const p = state.piers.find((q) => q.input.name === name);
|
const p = state.piers.find((q) => q.input.name === name);
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ describe('교대 받침 카드 (솔플레이트 결정 2026-11-06, h3 = 받침
|
|||||||
expect(rows.map((r) => r.S.toFixed(3))).toEqual(['-1.908', '-1.906', '-1.904', '-1.904', '-1.902']);
|
expect(rows.map((r) => r.S.toFixed(3))).toEqual(['-1.908', '-1.906', '-1.904', '-1.904', '-1.902']);
|
||||||
expect(rows.every((r) => r.source === '계산' && r.pair?.startsWith('P1-'))).toBe(true);
|
expect(rows.every((r) => r.source === '계산' && r.pair?.startsWith('P1-'))).toBe(true);
|
||||||
expect(rows[0].check).toBe('16 ≈ 15.3 일치');
|
expect(rows[0].check).toBe('16 ≈ 15.3 일치');
|
||||||
|
expect(rows.every((r) => r.azimuthSec !== undefined && r.azimuthSec <= 0.5)).toBe(true);
|
||||||
|
expect(abutSolplateRows(model, rule, H, { slopeI: rec.slopeI })[0].azimuthSec).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('without a pair the 교대 종단경사 i (−0.0191) is used: S −1.91 % (입력), T 30', () => {
|
it('without a pair the 교대 종단경사 i (−0.0191) is used: S −1.91 % (입력), T 30', () => {
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ describe('솔플레이트 TABLE (모델값, 2026-11-03)', () => {
|
|||||||
expect(rows[0].checkRhs).toBeCloseTo(15.265, 2);
|
expect(rows[0].checkRhs).toBeCloseTo(15.265, 2);
|
||||||
// 후열 ⑥: no pair (P2 not loaded) → the front twin ① value, 가정 + WS03.
|
// 후열 ⑥: no pair (P2 not loaded) → the front twin ① value, 가정 + WS03.
|
||||||
expect(rows[5]).toMatchObject({ source: '가정', pair: undefined, T: 30 });
|
expect(rows[5]).toMatchObject({ source: '가정', pair: undefined, T: 30 });
|
||||||
|
// 방위각 검사 (″, Q-SH4): front row against the A1 direction ≤ 0.5″, back row has no pair.
|
||||||
|
expect(rows.slice(0, 5).every((r) => r.azimuthSec !== undefined && r.azimuthSec <= 0.5)).toBe(true);
|
||||||
|
expect(rows[5].azimuthSec).toBeUndefined();
|
||||||
expect(rows[5].warnings).toContain('WS03');
|
expect(rows[5].warnings).toContain('WS03');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,11 +3,15 @@ import * as THREE from 'three';
|
|||||||
import { buildPier } from '../src/core/geometry';
|
import { buildPier } from '../src/core/geometry';
|
||||||
import { solplateGeometry } from '../src/core/solplate';
|
import { solplateGeometry } from '../src/core/solplate';
|
||||||
import { buildPierScene } from '../src/view/scene';
|
import { buildPierScene } from '../src/view/scene';
|
||||||
import { loadP1, p1Pairs } from './helpers';
|
import { loadInputWorkbook, loadP1, p1Pairs } from './helpers';
|
||||||
|
import { parseAbutments } from '../src/core/abut/parse';
|
||||||
|
import { girderPairs } from '../src/core/bearing-pair';
|
||||||
|
|
||||||
// Bridge context (A1 ↔ P1 girder pairs) as in the app, so the solplate slope is computed (2026-11-06).
|
// Bridge context (A1 ↔ P1 girder pairs) as in the app, so the solplate slope is computed (2026-11-06).
|
||||||
const P1CTX = { pairs: p1Pairs() };
|
const P1CTX = { pairs: p1Pairs() };
|
||||||
const FRONT = { 1: 3, 2: 3, 3: 3, 4: 3, 5: 3 };
|
const FRONT = { 1: 3, 2: 3, 3: 3, 4: 3, 5: 3 };
|
||||||
|
const a1 = parseAbutments(loadInputWorkbook()).abutments.find((a) => a.name === 'A1')!;
|
||||||
|
const A1S = { name: 'A1', bearings: a1.bearings.map((b) => ({ no: b.no, x: b.pos.e, y: b.pos.n, z: b.z, azimuthDeg: b.azimuthDeg! })) };
|
||||||
|
|
||||||
describe('솔플레이트 모델과 3D (결정 2026-11-06, Q-SH1–9)', () => {
|
describe('솔플레이트 모델과 3D (결정 2026-11-06, Q-SH1–9)', () => {
|
||||||
it('every bearing: h3 181, L 800 × W 740 (A 700 × B 640 + 100), T 30, T1 38 / T2 22', () => {
|
it('every bearing: h3 181, L 800 × W 740 (A 700 × B 640 + 100), T 30, T1 38 / T2 22', () => {
|
||||||
@@ -27,6 +31,17 @@ describe('솔플레이트 모델과 3D (결정 2026-11-06, Q-SH1–9)', () => {
|
|||||||
expect(m.blocks.find((b) => b.no === 6)!.plan.T).toBe(30);
|
expect(m.blocks.find((b) => b.no === 6)!.plan.T).toBe(30);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('방위각 검사: P1 ③ azimuth typed as 75°59′ (CAD) shows ≈ 87″ and WS01 on that bearing only', () => {
|
||||||
|
const p1 = loadP1();
|
||||||
|
const typo = { ...p1, bearings: p1.bearings.map((b) => (b.no === 3 ? { ...b, azimuthDeg: 90 - (75 + 59 / 60) } : b)) };
|
||||||
|
const pairs = girderPairs([A1S, { name: 'P1', bearings: typo.bearings.map((b) => ({ no: b.no, x: b.x, y: b.y, z: b.z, azimuthDeg: b.azimuthDeg })) }]);
|
||||||
|
const m = buildPier(typo, { pairs });
|
||||||
|
const s3 = m.soles.find((s) => s.no === 3)!;
|
||||||
|
expect(s3.azimuthCheckSec!).toBeGreaterThan(80);
|
||||||
|
expect(s3.warnings).toContain('WS01');
|
||||||
|
expect(m.soles.filter((s) => s.no !== 3 && s.row === '전열').every((s) => s.azimuthCheckSec! <= 0.5 && !s.warnings.includes('WS01'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('WS02: T typed 30 at S 3 % leaves T2 18 < 22', () => {
|
it('WS02: T typed 30 at S 3 % leaves T2 18 < 22', () => {
|
||||||
const m = buildPier({ ...loadP1(), sole: { slopePct: FRONT, tByRow: { 전열: 30 } } }, P1CTX);
|
const m = buildPier({ ...loadP1(), sole: { slopePct: FRONT, tByRow: { 전열: 30 } } }, P1CTX);
|
||||||
const s = m.soles.find((x) => x.no === 1)!;
|
const s = m.soles.find((x) => x.no === 1)!;
|
||||||
|
|||||||
Reference in new issue
Block a user