feat(bearing): 받침별 받침높이로 h3를 정한다 (교각·교대, AP07 폐지)
받침 시트의 받침높이_mm(9열)를 받침마다 읽어 h3 = H − T_ref로 쓴다. 거더 하면 = z + H가 받침마다 지켜진다. 솔플레이트 입력에 h3를 적으면 그 값이 모든 받침에 우선한다. 받침높이가 없으면 기존 기본값(교각 181, 교대 교대 값 − T_ref). 교대는 받침마다 달라도 받침별 값을 쓰므로 AP07(첫 값 사용) 경고를 없앤다. 교각 받침높이가 T_ref 이하이면 S01(E). 입력 양식 받침 표에 받침높이_mm 열을 추가한다. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8aedc9b969
commit
d7a61df7a9
10 files changed
+121
-13
No files matched your search
@@ -4,7 +4,7 @@
|
|||||||
* - 솔플레이트 (결정 2026-11-06, spec_abut §5·QA-15, src/core/sole-plate.ts): S = 같은 거더의 짝 받침(A1 ↔ P1 전열,
|
* - 솔플레이트 (결정 2026-11-06, spec_abut §5·QA-15, src/core/sole-plate.ts): S = 같은 거더의 짝 받침(A1 ↔ P1 전열,
|
||||||
* A2 ↔ Pn)으로 계산, 짝이 없으면 교대 종단경사 i(엑셀 Abut). L = A + 100, W = B + 100 (A × B 기본 700 × 640, 교각 P1
|
* A2 ↔ Pn)으로 계산, 짝이 없으면 교대 종단경사 i(엑셀 Abut). L = A + 100, W = B + 100 (A × B 기본 700 × 640, 교각 P1
|
||||||
* 전열 값 가정). 받침이 1열이라 T는 교대 전체에 하나: max(22, ⌈22 + L·|S|/2⌉). 거더 하면 고정 → 블럭 상면 = z − (T − T_ref).
|
* 전열 값 가정). 받침이 1열이라 T는 교대 전체에 하나: max(22, ⌈22 + L·|S|/2⌉). 거더 하면 고정 → 블럭 상면 = z − (T − T_ref).
|
||||||
* - h3 기본값 = 받침 시트의 받침높이 − T_ref (A1 275 − 30 = 245): 엑셀 받침높이가 정한 거더 하면을 그대로 둔다
|
* - h3 기본값 = 받침 시트의 받침높이 − T_ref (A1 275 − 30 = 245), 받침마다 그 받침의 값 (2026-11-10): 엑셀 받침높이가 정한 거더 하면을 그대로 둔다
|
||||||
* (시안은 교각 P1의 h3 181을 가정했으나 교대 시트에 받침높이가 있어 그 값을 지킨다).
|
* (시안은 교각 P1의 h3 181을 가정했으나 교대 시트에 받침높이가 있어 그 값을 지킨다).
|
||||||
* - 높이: E = h1 (블럭 콘크리트) + h2 (몰탈패드 50) + h3 + h4 (= T). z = 패드 상면 = EL.X.
|
* - 높이: E = h1 (블럭 콘크리트) + h2 (몰탈패드 50) + h3 + h4 (= T). z = 패드 상면 = EL.X.
|
||||||
*/
|
*/
|
||||||
@@ -62,7 +62,9 @@ export function abutSoles(model: AbutModel, rule: AbutBearingRule, heightMm: num
|
|||||||
model.name,
|
model.name,
|
||||||
model.bearings.map((b) => ({ no: b.no, row: '1열', azimuthDeg: b.azimuthDeg ?? 0, inputAzimuthDeg: ctx.inputAzimuthDeg?.[String(b.no)] })),
|
model.bearings.map((b) => ({ no: b.no, row: '1열', azimuthDeg: b.azimuthDeg ?? 0, inputAzimuthDeg: ctx.inputAzimuthDeg?.[String(b.no)] })),
|
||||||
{
|
{
|
||||||
h3Mm: rule.h3Mm ?? heightMm - rule.tRefMm, tRefMm: rule.tRefMm, tMinMm: rule.tMinMm, marginMm: rule.marginMm, plates,
|
// A typed h3 is for every bearing; otherwise h3 = 그 받침의 받침높이 (없으면 교대 값) − T_ref.
|
||||||
|
h3Mm: rule.h3Mm, tRefMm: rule.tRefMm,
|
||||||
|
h3ByNo: Object.fromEntries(model.bearings.map((b) => [String(b.no), (b.heightMm ?? heightMm) - rule.tRefMm])), tMinMm: rule.tMinMm, marginMm: rule.marginMm, plates,
|
||||||
tByRow: rule.tMm === undefined ? undefined : { '1열': rule.tMm },
|
tByRow: rule.tMm === undefined ? undefined : { '1열': rule.tMm },
|
||||||
slopePct: rule.slopePct === undefined ? undefined : Object.fromEntries(model.bearings.map((b) => [String(b.no), rule.slopePct!])),
|
slopePct: rule.slopePct === undefined ? undefined : Object.fromEntries(model.bearings.map((b) => [String(b.no), rule.slopePct!])),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export interface AbutBearing {
|
|||||||
seatEl?: number;
|
seatEl?: number;
|
||||||
/** 받침 방위각 (deg, model-xy: 북에서 시계방향, 교각과 같은 변환 swapXYAzimuth). 없으면 블럭은 교대축에 맞춘다. */
|
/** 받침 방위각 (deg, model-xy: 북에서 시계방향, 교각과 같은 변환 swapXYAzimuth). 없으면 블럭은 교대축에 맞춘다. */
|
||||||
azimuthDeg?: number;
|
azimuthDeg?: number;
|
||||||
|
/** 이 받침의 받침높이 H (mm, 2026-11-10). 없으면 교대 받침높이를 쓴다. */
|
||||||
|
heightMm?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 날개벽 형식, 측별(spec_abut §4.7): 일반형 / 단축형 / 없음. */
|
/** 날개벽 형식, 측별(spec_abut §4.7): 일반형 / 단축형 / 없음. */
|
||||||
@@ -87,6 +89,8 @@ export interface AbutBearingModel {
|
|||||||
blockHeightMm: number;
|
blockHeightMm: number;
|
||||||
/** 받침 방위각 (deg, model-xy). 입력이 없으면 undefined. */
|
/** 받침 방위각 (deg, model-xy). 입력이 없으면 undefined. */
|
||||||
azimuthDeg?: number;
|
azimuthDeg?: number;
|
||||||
|
/** 이 받침의 받침높이 H (mm). 입력이 없으면 undefined. */
|
||||||
|
heightMm?: number;
|
||||||
/** 블럭 평면 회전 (deg, −90 … 90): 받침 방위각과 교대축 직각(전면) 방향 사이의 각. 장면에서는 rotateY(이 값). 입력이 없으면 0. */
|
/** 블럭 평면 회전 (deg, −90 … 90): 받침 방위각과 교대축 직각(전면) 방향 사이의 각. 장면에서는 rotateY(이 값). 입력이 없으면 0. */
|
||||||
rotDeg: number;
|
rotDeg: number;
|
||||||
}
|
}
|
||||||
@@ -164,7 +168,7 @@ export function buildAbut(input: AbutInput): AbutModel {
|
|||||||
const s = dot(sub(b.pos, input.ip1), u);
|
const s = dot(sub(b.pos, input.ip1), u);
|
||||||
const seatEl = b.seatEl ?? input.seatEl + (b.z - minZ);
|
const seatEl = b.seatEl ?? input.seatEl + (b.z - minZ);
|
||||||
const blockHeightMm = Math.round((b.z - seatEl) * 1000);
|
const blockHeightMm = Math.round((b.z - seatEl) * 1000);
|
||||||
return { no: b.no, s, lateral: lateral[i], z: b.z, seatEl, blockHeightMm, azimuthDeg: b.azimuthDeg, rotDeg: bearingRotDeg(b.azimuthDeg, u, frontVec) };
|
return { no: b.no, s, lateral: lateral[i], z: b.z, seatEl, blockHeightMm, azimuthDeg: b.azimuthDeg, heightMm: b.heightMm, rotDeg: bearingRotDeg(b.azimuthDeg, u, frontVec) };
|
||||||
});
|
});
|
||||||
|
|
||||||
const sorted = [...bearings].sort((a, b) => a.s - b.s);
|
const sorted = [...bearings].sort((a, b) => a.s - b.s);
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export interface AbutRecord {
|
|||||||
ip2: AbutPoint;
|
ip2: AbutPoint;
|
||||||
ip3: AbutPoint;
|
ip3: AbutPoint;
|
||||||
bearings: AbutRecordBearing[];
|
bearings: AbutRecordBearing[];
|
||||||
/** 받침 행에서 읽은 받침높이 (mm). 모든 받침이 같아야 하며, 다르면 AP07 경고. */
|
/** 첫 받침의 받침높이 (mm): 받침별 값이 없을 때의 기본값. 받침마다 다르면 받침별 값을 쓴다 (2026-11-10, AP07 폐지). */
|
||||||
bearingHeightMm: number;
|
bearingHeightMm: number;
|
||||||
/** 측별 날개벽 형식 (편집 카드에서 정함). Absent = 양측 일반형. */
|
/** 측별 날개벽 형식 (편집 카드에서 정함). Absent = 양측 일반형. */
|
||||||
wings?: { left: WingForm; right: WingForm };
|
wings?: { left: WingForm; right: WingForm };
|
||||||
@@ -220,9 +220,8 @@ export function parseAbutments(wb: XLSX.WorkBook, reservedNames: readonly string
|
|||||||
const missing = [ab.ip1, ab.ip2, ab.ip3].some((p) => !Number.isFinite(p.e) || !Number.isFinite(p.n));
|
const missing = [ab.ip1, ab.ip2, ab.ip3].some((p) => !Number.isFinite(p.e) || !Number.isFinite(p.n));
|
||||||
if (missing) issues.push({ code: 'AP05', level: 'E', pier: ab.name, sheet: ab.sheet, message: `교대 ${ab.name}: IP1·IP2·IP3 중 빠진 점이 있습니다.` });
|
if (missing) issues.push({ code: 'AP05', level: 'E', pier: ab.name, sheet: ab.sheet, message: `교대 ${ab.name}: IP1·IP2·IP3 중 빠진 점이 있습니다.` });
|
||||||
if (ab.bearings.length < 2) issues.push({ code: 'AP06', level: 'E', pier: ab.name, sheet: ab.sheet, message: `교대 ${ab.name}: 받침이 2개 이상 필요합니다.` });
|
if (ab.bearings.length < 2) issues.push({ code: 'AP06', level: 'E', pier: ab.name, sheet: ab.sheet, message: `교대 ${ab.name}: 받침이 2개 이상 필요합니다.` });
|
||||||
const heights = [...new Set(ab.bearings.map((b) => b.bearingHeightMm))];
|
// 받침높이가 받침마다 달라도 받침별 값을 쓴다 (2026-11-10). 교대 값은 첫 받침의 값.
|
||||||
if (heights.length > 1) issues.push({ code: 'AP07', level: 'W', pier: ab.name, sheet: ab.sheet, message: `교대 ${ab.name}: 받침높이가 받침마다 다릅니다 (${heights.join(', ')} mm). 첫 값을 씁니다.` });
|
ab.bearingHeightMm = ab.bearings[0]?.bearingHeightMm ?? NaN;
|
||||||
ab.bearingHeightMm = heights[0] ?? NaN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { abutments: abutments.filter((a) => Number.isFinite(a.ip1.e) && a.bearings.length >= 2), issues };
|
return { abutments: abutments.filter((a) => Number.isFinite(a.ip1.e) && a.bearings.length >= 2), issues };
|
||||||
@@ -238,7 +237,7 @@ export function toAbutInput(rec: AbutRecord): AbutInput {
|
|||||||
ip2: rec.ip2,
|
ip2: rec.ip2,
|
||||||
ip3: rec.ip3,
|
ip3: rec.ip3,
|
||||||
// 계산 방위각을 적용한 받침 (2026-11-06) use that value; the record keeps the workbook value.
|
// 계산 방위각을 적용한 받침 (2026-11-06) use that value; the record keeps the workbook value.
|
||||||
bearings: rec.bearings.map((b) => ({ no: b.no, pos: b.pos, z: b.z, seatEl: b.seatEl, azimuthDeg: rec.azimuthApplied?.[String(b.no)] ?? b.azimuthDeg })),
|
bearings: rec.bearings.map((b) => ({ no: b.no, pos: b.pos, z: b.z, seatEl: b.seatEl, azimuthDeg: rec.azimuthApplied?.[String(b.no)] ?? b.azimuthDeg, heightMm: Number.isFinite(b.bearingHeightMm) ? b.bearingHeightMm : undefined })),
|
||||||
skewDeg: rec.skewDeg,
|
skewDeg: rec.skewDeg,
|
||||||
slopeI: rec.slopeI,
|
slopeI: rec.slopeI,
|
||||||
wings: rec.wings,
|
wings: rec.wings,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { type FootingRebarSettings, DEFAULT_FOOTING_REBAR } from './rebar/footin
|
|||||||
import { layoutText, resolveRowCounts, rowLayout, type PileGeometry } from './piles';
|
import { layoutText, resolveRowCounts, rowLayout, type PileGeometry } from './piles';
|
||||||
import type { PierInput, Row, SolplatePlan } from './types';
|
import type { PierInput, Row, SolplatePlan } from './types';
|
||||||
import type { BearingPair } from './bearing-pair';
|
import type { BearingPair } from './bearing-pair';
|
||||||
import { type BearingSole, structureSoles } from './sole-plate';
|
import { type BearingSole, SOLE_DEFAULTS, structureSoles } from './sole-plate';
|
||||||
import { type CopingGeom, P1_COPING_GEOM } from './coping-geom';
|
import { type CopingGeom, P1_COPING_GEOM } from './coping-geom';
|
||||||
import { castShape } from './cast-in-place-shape';
|
import { castShape } from './cast-in-place-shape';
|
||||||
|
|
||||||
@@ -467,8 +467,11 @@ export function buildPier(input: PierInput, ctx: PierContext = {}): PierModel {
|
|||||||
const skewOf = (no: number, k: number) => rowSkewDeg(bearingAz.get(no) ?? NaN, axis.segments[k].u);
|
const skewOf = (no: number, k: number) => rowSkewDeg(bearingAz.get(no) ?? NaN, axis.segments[k].u);
|
||||||
// Solplate of every bearing (결정 2026-11-06): S from the girder pair (ctx.pairs over the whole bridge), else the
|
// Solplate of every bearing (결정 2026-11-06): S from the girder pair (ctx.pairs over the whole bridge), else the
|
||||||
// 종단경사 input, else the other row; T per row; the girder underside stays, so the block top drops by T − T_ref.
|
// 종단경사 input, else the other row; T per row; the girder underside stays, so the block top drops by T − T_ref.
|
||||||
|
// 받침높이 per bearing (2026-11-10): h3 = H − T_ref keeps the girder underside at z + H; a typed h3 still wins.
|
||||||
|
const tRef = input.sole?.tRefMm ?? SOLE_DEFAULTS.tRefMm;
|
||||||
|
const h3ByNo = Object.fromEntries(input.bearings.filter((b) => b.heightMm !== undefined).map((b) => [String(b.no), b.heightMm! - tRef]));
|
||||||
const soles = new Map(structureSoles(input.name, input.bearings.map((b) => ({ no: b.no, row: b.row, azimuthDeg: azimuthOf(b), inputAzimuthDeg: b.azimuthDeg })),
|
const soles = new Map(structureSoles(input.name, input.bearings.map((b) => ({ no: b.no, row: b.row, azimuthDeg: azimuthOf(b), inputAzimuthDeg: b.azimuthDeg })),
|
||||||
input.sole, ctx.pairs ?? [], input.longitudinalSlope === undefined ? undefined : input.longitudinalSlope * 100).map((s) => [s.no, s]));
|
{ ...input.sole, h3ByNo }, ctx.pairs ?? [], input.longitudinalSlope === undefined ? undefined : input.longitudinalSlope * 100).map((s) => [s.no, s]));
|
||||||
const planOf = (no: number, k: number, skewDeg: number): SolplatePlan => {
|
const planOf = (no: number, k: number, skewDeg: number): SolplatePlan => {
|
||||||
const s = soles.get(no)!;
|
const s = soles.get(no)!;
|
||||||
// The block's c axis in (E, N): (−sin d)·u + (cos d)·nrm; T1 goes to the end of c that points uphill.
|
// The block's c axis in (E, N): (−sin d)·u + (cos d)·nrm; T1 goes to the end of c that points uphill.
|
||||||
|
|||||||
@@ -188,5 +188,7 @@ function toBearing(c: Cell[]): Bearing {
|
|||||||
row: String(c[6]) as Row,
|
row: String(c[6]) as Row,
|
||||||
// 교좌면_EL (m), optional: blank = not given, so the model keeps the 두부보 상면 slope.
|
// 교좌면_EL (m), optional: blank = not given, so the model keeps the 두부보 상면 slope.
|
||||||
seatEl: c[7] === undefined || c[7] === null || c[7] === '' ? undefined : Number(c[7]),
|
seatEl: c[7] === undefined || c[7] === null || c[7] === '' ? undefined : Number(c[7]),
|
||||||
|
// 받침높이_mm, optional (2026-11-10): blank = the pier h3 setting.
|
||||||
|
heightMm: c[8] === undefined || c[8] === null || c[8] === '' ? undefined : Number(c[8]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,10 @@
|
|||||||
import { type BearingPair, pairOf } from './bearing-pair';
|
import { type BearingPair, pairOf } from './bearing-pair';
|
||||||
|
|
||||||
export interface SoleSettings {
|
export interface SoleSettings {
|
||||||
/** 받침 본체 높이 h3 (mm). */
|
/** 받침 본체 높이 h3 (mm), typed for the whole structure. It wins over h3ByNo. */
|
||||||
h3Mm?: number;
|
h3Mm?: number;
|
||||||
|
/** h3 per bearing number (mm), from that bearing's 받침높이 − T_ref (2026-11-10). Absent = h3Mm or the default. */
|
||||||
|
h3ByNo?: Record<string, number>;
|
||||||
/** T_ref: the solplate thickness the workbook z assumed (mm). */
|
/** T_ref: the solplate thickness the workbook z assumed (mm). */
|
||||||
tRefMm?: number;
|
tRefMm?: number;
|
||||||
/** T_min: the thin-end minimum (mm). */
|
/** T_min: the thin-end minimum (mm). */
|
||||||
@@ -130,7 +132,7 @@ export function structureSoles(structure: string, bearings: SoleBearing[], setti
|
|||||||
azimuthInputDeg: b.inputAzimuthDeg ?? b.azimuthDeg,
|
azimuthInputDeg: b.inputAzimuthDeg ?? b.azimuthDeg,
|
||||||
azimuthCalcDeg: pair ? (pair.from.structure === structure && pair.from.no === b.no ? pair.fromCalcDeg : pair.toCalcDeg) : undefined,
|
azimuthCalcDeg: pair ? (pair.from.structure === structure && pair.from.no === b.no ? pair.fromCalcDeg : pair.toCalcDeg) : undefined,
|
||||||
azimuthCheckSec: pair ? (pair.from.structure === structure && pair.from.no === b.no ? pair.fromCheckSec : pair.toCheckSec) : 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: settings.h3Mm ?? s.h3ByNo?.[String(b.no)] ?? 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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ export function buildTemplateWorkbook(): XLSX.WorkBook {
|
|||||||
const bearing = [
|
const bearing = [
|
||||||
note('받침 1행당 받침 1개. 전열·후열 수는 같아야 합니다. 위치는 전열 또는 후열.'),
|
note('받침 1행당 받침 1개. 전열·후열 수는 같아야 합니다. 위치는 전열 또는 후열.'),
|
||||||
note('z_m은 받침 EL(m), 방위각은 거더 방향입니다.'),
|
note('z_m은 받침 EL(m), 방위각은 거더 방향입니다.'),
|
||||||
['교각명', '받침번호', 'x_m', 'y_m', 'z_m', '방위각', '위치', '교좌면_EL'],
|
note('받침높이_mm는 패드 상면(z)에서 솔플레이트 상면까지입니다. 비우면 교각의 받침 높이 h3 설정을 씁니다.'),
|
||||||
|
['교각명', '받침번호', 'x_m', 'y_m', 'z_m', '방위각', '위치', '교좌면_EL', '받침높이_mm'],
|
||||||
];
|
];
|
||||||
|
|
||||||
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(pier), '교각');
|
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(pier), '교각');
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ export interface Bearing {
|
|||||||
row: Row;
|
row: Row;
|
||||||
/** 교좌면 EL (m): the block bottom under this bearing. Absent = the 두부보 상면 slope (the coping top). */
|
/** 교좌면 EL (m): the block bottom under this bearing. Absent = the 두부보 상면 slope (the coping top). */
|
||||||
seatEl?: number;
|
seatEl?: number;
|
||||||
|
/** 받침높이 H (mm) of this bearing: pad top (z) to solplate top, so h3 = H − T_ref (2026-11-10). Absent = the pier h3. */
|
||||||
|
heightMm?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PierInput {
|
export interface PierInput {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { integratedCheck } from './bearing-layout';
|
|||||||
import { checkBlockRebar } from './rebar/block-check';
|
import { checkBlockRebar } from './rebar/block-check';
|
||||||
import { castSettingsIssues } from './rebar/cast-in-place';
|
import { castSettingsIssues } from './rebar/cast-in-place';
|
||||||
import { castShape, castShapeIssues } from './cast-in-place-shape';
|
import { castShape, castShapeIssues } from './cast-in-place-shape';
|
||||||
|
import { SOLE_DEFAULTS } from './sole-plate';
|
||||||
|
|
||||||
const EXPECTED_COLUMNS: Record<PierType, number> = { 'T형': 1, '2주형': 2, '3주형': 3 };
|
const EXPECTED_COLUMNS: Record<PierType, number> = { 'T형': 1, '2주형': 2, '3주형': 3 };
|
||||||
|
|
||||||
@@ -34,6 +35,15 @@ export function validateInput(input: PierInput, issues: Issue[]): boolean {
|
|||||||
});
|
});
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
|
// 받침높이 per bearing (2026-11-10): h3 = H − T_ref must be positive unless the pier h3 is typed.
|
||||||
|
if (input.sole?.h3Mm === undefined) {
|
||||||
|
const tRef = input.sole?.tRefMm ?? SOLE_DEFAULTS.tRefMm;
|
||||||
|
for (const b of input.bearings) {
|
||||||
|
if (b.heightMm === undefined || (Number.isFinite(b.heightMm) && b.heightMm - tRef > 0)) continue;
|
||||||
|
issues.push({ code: 'S01', level: 'E', pier: input.name, message: `받침 ${b.no}번 받침높이 ${b.heightMm} mm가 T_ref ${tRef} mm보다 커야 받침 본체 h3를 만들 수 있습니다.` });
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
const front = input.bearings.filter((b) => b.row === '전열').length;
|
const front = input.bearings.filter((b) => b.row === '전열').length;
|
||||||
const back = input.bearings.filter((b) => b.row === '후열').length;
|
const back = input.bearings.filter((b) => b.row === '후열').length;
|
||||||
if (front === 0 || back === 0) {
|
if (front === 0 || back === 0) {
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { buildPier } from '../src/core/geometry';
|
||||||
|
import { validateInput } from '../src/core/validate';
|
||||||
|
import { parseAbutments, toAbutInput } from '../src/core/abut/parse';
|
||||||
|
import { buildAbut } from '../src/core/abut/geometry';
|
||||||
|
import { abutSoles, resolveBearingRule } from '../src/core/abut/bearing-table';
|
||||||
|
import type { Issue, PierInput } from '../src/core/types';
|
||||||
|
import { loadInputWorkbook, loadP1, p1Pairs } from './helpers';
|
||||||
|
|
||||||
|
// 받침별 받침높이 (2026-11-10): h3 = 그 받침의 받침높이 H − T_ref, 거더 하면 = z + H.
|
||||||
|
const CTX = { pairs: p1Pairs() };
|
||||||
|
const withHeights = (input: PierInput, h: Record<number, number | undefined>): PierInput => ({
|
||||||
|
...input,
|
||||||
|
bearings: input.bearings.map((b) => ({ ...b, heightMm: b.no in h ? h[b.no] : b.heightMm })),
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('교각 받침별 받침높이', () => {
|
||||||
|
it('reads 받침높이 from the bearing sheet (P1: 211 for every bearing, so h3 stays 181)', () => {
|
||||||
|
const p1 = loadP1();
|
||||||
|
expect(p1.bearings.map((b) => b.heightMm)).toEqual(Array(10).fill(211));
|
||||||
|
expect(buildPier(p1, CTX).blocks.every((b) => b.plan.h3 === 181)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('each bearing gets h3 = its H − T_ref; the block (z − pad − drop) does not move', () => {
|
||||||
|
const base = buildPier(loadP1(), CTX);
|
||||||
|
const m = buildPier(withHeights(loadP1(), { 3: 174, 8: 188 }), CTX);
|
||||||
|
const h3 = (no: number) => m.blocks.find((b) => b.no === no)!.plan.h3;
|
||||||
|
expect(h3(3)).toBe(144);
|
||||||
|
expect(h3(8)).toBe(158);
|
||||||
|
expect(h3(1)).toBe(181);
|
||||||
|
for (const b of m.blocks) {
|
||||||
|
const o = base.blocks.find((x) => x.no === b.no)!;
|
||||||
|
expect(b.top).toBeCloseTo(o.top, 9);
|
||||||
|
expect(b.bottom).toBeCloseTo(o.bottom, 9);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('girder underside = z + H: pad top + h3 + T − (T − T_ref) − … equals z + H for every bearing', () => {
|
||||||
|
const input = withHeights(loadP1(), { 2: 176, 7: 183 });
|
||||||
|
const m = buildPier(input, CTX);
|
||||||
|
for (const b of m.blocks) {
|
||||||
|
const H = input.bearings.find((x) => x.no === b.no)!.heightMm!;
|
||||||
|
const z = input.bearings.find((x) => x.no === b.no)!.z;
|
||||||
|
expect(b.padTop + (b.plan.h3 + b.plan.T) / 1000).toBeCloseTo(z + H / 1000, 9);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a typed h3 (솔플레이트 입력) wins over the bearing heights', () => {
|
||||||
|
const input = { ...withHeights(loadP1(), { 3: 174 }), sole: { h3Mm: 200 } };
|
||||||
|
expect(buildPier(input, CTX).blocks.every((b) => b.plan.h3 === 200)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('without 받침높이 the default h3 181 is used', () => {
|
||||||
|
const input = { ...loadP1(), bearings: loadP1().bearings.map(({ heightMm: _h, ...b }) => b) };
|
||||||
|
expect(buildPier(input, CTX).blocks.every((b) => b.plan.h3 === 181)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('S01 (E) when a 받침높이 is not above T_ref, so no body could be built', () => {
|
||||||
|
const issues: Issue[] = [];
|
||||||
|
expect(validateInput(withHeights(loadP1(), { 4: 30 }), issues)).toBe(false);
|
||||||
|
expect(issues.filter((i) => i.code === 'S01').map((i) => i.message)).toEqual([expect.stringContaining('받침 4번')]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('교대 받침별 받침높이', () => {
|
||||||
|
const a1 = () => parseAbutments(loadInputWorkbook()).abutments.find((a) => a.name === 'A1')!;
|
||||||
|
|
||||||
|
it('different heights per bearing give different h3 and no AP07 warning', () => {
|
||||||
|
const wb = loadInputWorkbook();
|
||||||
|
wb.Sheets['받침']['I16'] = { t: 'n', v: 260 };
|
||||||
|
const { abutments, issues } = parseAbutments(wb);
|
||||||
|
expect(issues.some((i) => i.code === 'AP07')).toBe(false);
|
||||||
|
const rec = abutments.find((a) => a.name === 'A1')!;
|
||||||
|
const soles = abutSoles(buildAbut(toAbutInput(rec)), resolveBearingRule(), rec.bearingHeightMm);
|
||||||
|
expect(soles.map((s) => s.h3)).toEqual([245, 245, 230, 245, 245]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a typed h3 wins for every bearing', () => {
|
||||||
|
const rec = a1();
|
||||||
|
const soles = abutSoles(buildAbut(toAbutInput(rec)), resolveBearingRule({ h3Mm: 200 }), rec.bearingHeightMm);
|
||||||
|
expect(soles.every((s) => s.h3 === 200)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user