feat(abut): 접속슬래브 카드 — 매립형 / 노출형 형식, 경사, 치수 입력, 삽도 3개, 모따기 3D 반영
- 노출형 (삽도 기준, QA-8 가정): 상면 = 흉벽 상단, 끝은 브라켓 위(끝 두께 = 브라켓 높이), 헌치 길이 기본 2000 - 형식을 고르면 경사가 기본값으로 바뀜 (매립형 하향 4 %, 노출형 종단경사), 경사 타일로 변경 가능 - 길이 · 두께 · 흉벽과 간격 · 날개벽과 간격 입력이 3D, 폭, 날개벽 평면에 반영 - 슬래브 모따기 = 날개벽 헌치 (3D와 평면), 날개벽 없는 측은 skew 교좌면 끝선 - 3D 슬래브를 단면 기준 입체로 다시 만듦, 프로젝트 파일 저장 · 검증 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fc0370883f
commit
4b8b80ae97
14 files changed
+854
-60
No files matched your search
+50
-3
@@ -5,7 +5,8 @@
|
||||
import type { AbutRecord } from './parse';
|
||||
import type { WingForm } from './geometry';
|
||||
import { WING_DIMS_DEFAULT, wingDimsIssues, type WingDims } from './wings';
|
||||
import { shapeIssues, type AbutShape } from './shape';
|
||||
import { ABUT_SHAPE_DEFAULT, normalizeShape, shapeIssues, type AbutShape } from './shape';
|
||||
import { resolveSlab, slabDefaultSlope, slabIssues, type AbutSlab, type SlabForm, type SlabSlope } from './slab';
|
||||
import { abutBearingRuleIssues, resolveBearingRule, type AbutBearingRule } from './bearing-table';
|
||||
|
||||
export type AbutEditKey = 'seatEl' | 'foundationBottomEl' | 'groundEl' | 'pileLengthMm' | 'skewDeg' | 'slopeI';
|
||||
@@ -105,7 +106,7 @@ export function restoreAbutDefault(defaults: AbutRecord): AbutRecord {
|
||||
export function applyShapeEdits(rec: AbutRecord, values: Partial<AbutShape>): AbutRecord {
|
||||
for (const v of Object.values(values)) if (!Number.isFinite(v)) throw new Error('숫자를 입력하세요.');
|
||||
const shape = { ...(rec.shape ?? {}), ...values };
|
||||
const issues = shapeIssues(shape);
|
||||
const issues = [...shapeIssues(shape), ...slabFitIssues(rec.slab, shape)];
|
||||
if (issues.length > 0) throw new Error(issues[0]);
|
||||
return { ...rec, shape };
|
||||
}
|
||||
@@ -113,11 +114,57 @@ export function applyShapeEdits(rec: AbutRecord, values: Partial<AbutShape>): Ab
|
||||
export function applyShapeEdit(rec: AbutRecord, key: keyof AbutShape, value: number): AbutRecord {
|
||||
if (!Number.isFinite(value)) throw new Error('숫자를 입력하세요.');
|
||||
const shape = { ...(rec.shape ?? {}), [key]: value };
|
||||
const issues = shapeIssues(shape);
|
||||
const issues = [...shapeIssues(shape), ...slabFitIssues(rec.slab, shape)];
|
||||
if (issues.length > 0) throw new Error(issues[0]);
|
||||
return { ...rec, shape };
|
||||
}
|
||||
|
||||
/** 브라켓 높이 (mm) of a shape input, default when absent. */
|
||||
function bracketHeightMm(shape: Partial<AbutShape> | undefined): number {
|
||||
return { ...ABUT_SHAPE_DEFAULT, ...normalizeShape(shape) }.bracketHeightMm;
|
||||
}
|
||||
|
||||
/** A lower 브라켓 must still hold the 접속슬래브 under the 흉벽 상단. */
|
||||
function slabFitIssues(slab: Partial<AbutSlab> | undefined, shape: Partial<AbutShape>): string[] {
|
||||
const s = resolveSlab(slab);
|
||||
const h = bracketHeightMm(shape);
|
||||
return s.thicknessMm > h ? [`브라켓 높이(${h})는 접속슬래브 두께(${s.thicknessMm}) 이상이어야 합니다.`] : [];
|
||||
}
|
||||
|
||||
/** 접속슬래브 형식 (2026-11-04): picking a form also sets its slope (매립형 하향 4 %, 노출형 종단경사). */
|
||||
export function setSlabForm(rec: AbutRecord, form: SlabForm): AbutRecord {
|
||||
const slab: Partial<AbutSlab> = { ...(rec.slab ?? {}), form };
|
||||
delete slab.slope;
|
||||
const issues = slabIssues(resolveSlab(slab), bracketHeightMm(rec.shape));
|
||||
if (issues.length > 0) throw new Error(issues[0]);
|
||||
return { ...rec, slab };
|
||||
}
|
||||
|
||||
/** 접속슬래브 경사; the form's own slope is stored as "not set". */
|
||||
export function setSlabSlope(rec: AbutRecord, slope: SlabSlope): AbutRecord {
|
||||
const slab: Partial<AbutSlab> = { ...(rec.slab ?? {}), slope };
|
||||
if (slope === slabDefaultSlope(resolveSlab(rec.slab).form)) delete slab.slope;
|
||||
return { ...rec, slab };
|
||||
}
|
||||
|
||||
/** 접속슬래브 입력칸 (② 접속슬래브 카드, 시안 순서). 헌치 길이는 노출형만. */
|
||||
export const SLAB_EDIT_FIELDS: { key: 'lengthMm' | 'thicknessMm' | 'parapetGapMm' | 'wingGapMm' | 'haunchLengthMm'; label: string; exposedOnly?: boolean; note?: string }[] = [
|
||||
{ key: 'lengthMm', label: '길이 (mm)' },
|
||||
{ key: 'thicknessMm', label: '두께 (mm)' },
|
||||
{ key: 'parapetGapMm', label: '흉벽과 간격 (mm)' },
|
||||
{ key: 'wingGapMm', label: '날개벽과 간격 (mm)' },
|
||||
{ key: 'haunchLengthMm', label: '헌치 길이 (mm)', exposedOnly: true, note: '가정 · 삽도 비율' },
|
||||
];
|
||||
|
||||
/** Changes one 접속슬래브 dim, refused with a Korean message when the slab would not build. */
|
||||
export function applySlabEdit(rec: AbutRecord, key: (typeof SLAB_EDIT_FIELDS)[number]['key'], value: number): AbutRecord {
|
||||
if (!Number.isFinite(value)) throw new Error('숫자를 입력하세요.');
|
||||
const slab = { ...(rec.slab ?? {}), [key]: value };
|
||||
const issues = slabIssues(resolveSlab(slab), bracketHeightMm(rec.shape));
|
||||
if (issues.length > 0) throw new Error(issues[0]);
|
||||
return { ...rec, slab };
|
||||
}
|
||||
|
||||
|
||||
/** 형상 입력 fields on the 교대 카드 (시안 ② 벽체·흉벽, 확대기초·말뚝), in the order of the mockup. */
|
||||
export const SHAPE_EDIT_FIELDS: { key: keyof AbutShape; label: string; group: 'wall' | 'footing'; note?: string }[] = [
|
||||
|
||||
Reference in new issue
Block a user