feat(abut): 교대 형상·입력 패널과 교각 받침·솔플레이트 개편
- 교대 형상(src/core/abut, 3D 장면, 목록·카메라·맞춤)과 교대 입력 패널 추가 - 교각 받침 기준 카드 제거, 솔플레이트 규칙을 교각별 입력으로 변경 - 받침 ELEVATION·솔플레이트 TABLE을 모델값으로 연결 - 교대 입력값 카드: 형식 행에 시점·종점 표시, 제목을 "A1 입력값 [입력]"으로 변경 - 테스트 614건 통과, 타입 검사 통과 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b1b2ad1ddb
commit
1d31d2f3a9
44 files changed
+2571
-120
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import type { FckByMember, Material } from './rebar/common';
|
||||
import { checkStoredGroundModel, type GroundModelFile } from './ground-model';
|
||||
import type { PierInput } from './types';
|
||||
import type { AbutRecord } from './abut/parse';
|
||||
import { normalizeBearingAzimuths } from './coordinate-frame';
|
||||
|
||||
/** Project file schema (spec §8). Bump only with a migration. */
|
||||
@@ -37,6 +38,10 @@ export interface ProjectFile {
|
||||
fckByMember?: FckByMember;
|
||||
/** 지반 모델 (ground_model.json)의 교각별 지반고. Absent = 불러오지 않음. Applied to a pier only by the user. */
|
||||
groundModel?: GroundModelFile;
|
||||
/** 교대 (2026-11-03, AB): 교대 입력 기록. Absent in older files: no abutments. */
|
||||
abutments?: AbutRecord[];
|
||||
/** 교대 기본값 (가져온 그대로): 기본값 복원에 쓴다. Absent = 교대별 현재값이 기본값. */
|
||||
abutDefaults?: AbutRecord[];
|
||||
}
|
||||
|
||||
/** Serialises a project to the pretty-printed JSON stored in <교량명>_교각설계.pzr.json. */
|
||||
@@ -69,6 +74,8 @@ export function parseProject(text: string): ProjectFile {
|
||||
}
|
||||
}
|
||||
if (obj.groundModel !== undefined) checkStoredGroundModel(obj.groundModel);
|
||||
if (obj.abutments !== undefined) checkStoredAbutments(obj.abutments);
|
||||
if (obj.abutDefaults !== undefined) checkStoredAbutments(obj.abutDefaults);
|
||||
obj.piers.forEach((p, i) => {
|
||||
if (!p || typeof p.id !== 'string' || !p.input) throw new Error(`교각 ${i + 1}: id 또는 input이 없습니다.`);
|
||||
const input = p.input;
|
||||
@@ -99,3 +106,38 @@ export function parseProject(text: string): ProjectFile {
|
||||
});
|
||||
return obj as ProjectFile;
|
||||
}
|
||||
|
||||
const finite = (v: unknown) => typeof v === 'number' && Number.isFinite(v);
|
||||
const isPoint = (v: unknown) => !!v && typeof v === 'object' && finite((v as { e?: unknown }).e) && finite((v as { n?: unknown }).n);
|
||||
|
||||
/** 교대 기록 check (AB): refuses a damaged abutment record with a message that names it. */
|
||||
export function checkStoredAbutments(list: unknown): void {
|
||||
if (!Array.isArray(list)) throw new Error('교대 목록 형식이 맞지 않습니다.');
|
||||
list.forEach((a: Partial<AbutRecord>, i) => {
|
||||
const who = `교대 ${i + 1}(${String(a?.name)})`;
|
||||
if (!a || typeof a.name !== 'string' || !/^A\d+$/.test(a.name)) throw new Error(`${who}: 교대명이 맞지 않습니다.`);
|
||||
if (a.type !== 'A1' && a.type !== 'A2') throw new Error(`${who}: 형식은 A1 또는 A2여야 합니다.`);
|
||||
for (const key of ['seatEl', 'foundationBottomEl', 'groundEl', 'pileLengthMm', 'skewDeg', 'slopeI'] as const) {
|
||||
if (!finite(a[key])) throw new Error(`${who}: ${key} 값이 맞지 않습니다.`);
|
||||
}
|
||||
for (const key of ['ip1', 'ip2', 'ip3'] as const) {
|
||||
if (!isPoint(a[key])) throw new Error(`${who}: ${key} 좌표가 맞지 않습니다.`);
|
||||
}
|
||||
if (!Array.isArray(a.bearings) || a.bearings.length < 2) throw new Error(`${who}: 받침이 2개 이상 필요합니다.`);
|
||||
if (a.shape !== undefined) {
|
||||
for (const [key, v] of Object.entries(a.shape)) {
|
||||
if (!finite(v)) throw new Error(`${who}: 형상 값 ${key}이(가) 숫자가 아닙니다.`);
|
||||
}
|
||||
}
|
||||
if (a.wings !== undefined) {
|
||||
const forms = ['general', 'short', 'none'];
|
||||
if (!forms.includes(a.wings.left) || !forms.includes(a.wings.right)) throw new Error(`${who}: 날개벽 형식이 맞지 않습니다.`);
|
||||
}
|
||||
for (const b of a.bearings) {
|
||||
if (!b || !finite(b.no) || !isPoint(b.pos) || !finite(b.z) || (b.seatEl !== undefined && !finite(b.seatEl))) {
|
||||
throw new Error(`${who}: 받침 ${String(b?.no)}의 값이 맞지 않습니다.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user