feat(abut): 접속슬래브 경사는 형식이 정함, 단면 · 접합 상세를 파단선 단면 하나로 합침, 흉벽 높이 입력

- 경사 선택 삭제: 매립형 = 하향 4 %, 노출형 = 종단경사 따라 (노트 표기). 예전 파일의 slope 값은 읽고 무시
- 단면 + 접합 상세: 흉벽 쪽과 먼 끝만 그리고 중간 생략, 부속재 · 치수(7000 · 450 · 850 · 295 · 헌치) · EL을 한 그림에
- 흉벽 높이 (브라켓 상면 ~ 흉벽 상단) 입력 = 벽체·흉벽 카드 브라켓 높이와 같은 값
- 자동 값: 브라켓 얹힘 길이 = 브라켓 폭 − 흉벽과 간격 (295)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-04 10:27:20 +09:00
1 parent 4b8b80ae97
commit 9835fb42b3
7 files changed
+157 -199

No files matched your search

+5 -12
View File
@@ -6,7 +6,7 @@ import type { AbutRecord } from './parse';
import type { WingForm } from './geometry';
import { WING_DIMS_DEFAULT, wingDimsIssues, type WingDims } from './wings';
import { ABUT_SHAPE_DEFAULT, normalizeShape, shapeIssues, type AbutShape } from './shape';
import { resolveSlab, slabDefaultSlope, slabIssues, type AbutSlab, type SlabForm, type SlabSlope } from './slab';
import { resolveSlab, slabIssues, type AbutSlab, type SlabForm } from './slab';
import { abutBearingRuleIssues, resolveBearingRule, type AbutBearingRule } from './bearing-table';
export type AbutEditKey = 'seatEl' | 'foundationBottomEl' | 'groundEl' | 'pileLengthMm' | 'skewDeg' | 'slopeI';
@@ -131,22 +131,15 @@ function slabFitIssues(slab: Partial<AbutSlab> | undefined, shape: Partial<AbutS
return s.thicknessMm > h ? [`브라켓 높이(${h})는 접속슬래브 두께(${s.thicknessMm}) 이상이어야 합니다.`] : [];
}
/** 접속슬래브 형식 (2026-11-04): picking a form also sets its slope (매립형 하향 4 %, 노출형 종단경사). */
/** 접속슬래브 형식 (2026-11-04); the form also sets the slope (매립형 하향 4 %, 노출형 종단경사). */
export function setSlabForm(rec: AbutRecord, form: SlabForm): AbutRecord {
const slab: Partial<AbutSlab> = { ...(rec.slab ?? {}), form };
delete slab.slope;
const { slope: _old, ...kept } = (rec.slab ?? {}) as Partial<AbutSlab> & { slope?: unknown };
const slab: Partial<AbutSlab> = { ...kept, form };
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)' },
@@ -172,7 +165,7 @@ export const SHAPE_EDIT_FIELDS: { key: keyof AbutShape; label: string; group: 'w
{ key: 'wallFrontMm', label: '교좌면 폭 (mm)', group: 'wall' },
{ key: 'parapetHeightMm', label: '흉벽 높이 (mm)', group: 'wall' },
{ key: 'parapetThicknessMm', label: '흉벽 두께 (mm)', group: 'wall' },
{ key: 'bracketHeightMm', label: '브라켓 높이 (mm)', group: 'wall' },
{ key: 'bracketHeightMm', label: '브라켓 높이 (mm)', group: 'wall', note: '= 접속슬래브 카드 흉벽 높이' },
{ key: 'bracketWidthMm', label: '브라켓 폭 (mm)', group: 'wall' },
{ key: 'shrinkJointCount', label: '수축줄눈 개수', group: 'wall' },
{ key: 'parapetLevelLeftMm', label: '흉벽 상단 Level 좌 (mm)', group: 'wall', note: '기준 확인 QA-4' },
+8 -16
View File
@@ -4,17 +4,15 @@
* - 매립형 (type-1, 기본): 하면 = 브라켓 상면, 두께 일정. 브라켓 위(벽체 배면까지)는 수평이고 그 뒤로 경사(기본 하향 4 %).
* - 노출형 (type-2, 2026-11-04 삽도 기준, QA-8): 상면 = 흉벽 상단에서 경사(기본 종단경사)를 따른다. 끝은 브라켓 상면에
* 얹히고(끝 두께 = 브라켓 높이), 벽체 배면부터 헌치 길이 동안 두께가 줄어 그 뒤로는 두께가 일정하다.
* - 경사: 형식을 고르면 그 형식의 기본값(매립형 하향 4 %, 노출형 종단경사)으로 바뀌고, 따로 바꿀 수 있다.
* - 경사는 형식이 정한다 (2026-11-04): 매립형 = 하향 4 %, 노출형 = 종단경사 따라 (엑셀 A555 "type-1 = 하향경사 4 %, type-2 = 종단경사").
* - t: 교대축 직각 (전면 +, 배면 −, m). 슬래브는 흉벽 배면에서 "흉벽과 간격"만큼 띄워 시작하고 길이만큼 배면 쪽으로 간다.
* - 단면의 EL은 그 s의 흉벽 상단 EL에서 잰 값이다 (흉벽 상단 경사는 호출하는 쪽에서 더한다).
*/
export type SlabForm = 'buried' | 'exposed';
export type SlabSlope = 'down4' | 'grade';
export interface AbutSlab {
form: SlabForm;
slope: SlabSlope;
lengthMm: number;
thicknessMm: number;
parapetGapMm: number;
@@ -25,7 +23,6 @@ export interface AbutSlab {
export const ABUT_SLAB_DEFAULT: AbutSlab = {
form: 'buried',
slope: 'down4',
lengthMm: 7000,
thicknessMm: 450,
parapetGapMm: 5,
@@ -38,31 +35,26 @@ export const SLAB_FALL_PCT = 4;
export const SLAB_FORM_LABEL: Record<SlabForm, string> = { buried: '매립형', exposed: '노출형' };
/** The slope a form starts with: 매립형 하향 4 %, 노출형 종단경사 (엑셀 A555 "type-1 = 하향경사 4 %, type-2 = 종단경사"). */
export function slabDefaultSlope(form: SlabForm): SlabSlope {
return form === 'exposed' ? 'grade' : 'down4';
}
/** The user's values over the defaults; the slope follows the form unless it was set. */
/** The user's values over the defaults. A `slope` stored by an earlier version (2026-11-04 오전) is dropped: the form sets it. */
export function resolveSlab(changes?: Partial<AbutSlab>): AbutSlab {
const form = changes?.form ?? ABUT_SLAB_DEFAULT.form;
return { ...ABUT_SLAB_DEFAULT, slope: slabDefaultSlope(form), ...(changes ?? {}) };
const { slope: _old, ...rest } = (changes ?? {}) as Partial<AbutSlab> & { slope?: unknown };
return { ...ABUT_SLAB_DEFAULT, ...rest };
}
/** Keys whose value differs from the default (형식, 경사 and the dims), for the change marks. */
/** Keys whose value differs from the default (형식 and the dims), for the change marks. */
export function slabChangedKeys(changes?: Partial<AbutSlab>): (keyof AbutSlab)[] {
const s = resolveSlab(changes);
return (Object.keys(ABUT_SLAB_DEFAULT) as (keyof AbutSlab)[]).filter((k) => s[k] !== ABUT_SLAB_DEFAULT[k]);
}
/** Slope (%), + = rises away from the wall: 하향 4 % → −4, 종단경사 → the wing slope (A1 = −i, A2 = +i). */
/** Slope (%), + = rises away from the wall: 매립형 하향 4 % → −4, 노출형 종단경사 → the wing slope (A1 = −i, A2 = +i). */
export function slabGradePct(slab: AbutSlab, wingSlopePct: number): number {
return slab.slope === 'down4' ? -SLAB_FALL_PCT : wingSlopePct;
return slab.form === 'buried' ? -SLAB_FALL_PCT : wingSlopePct;
}
/** Slope text as on the card: "하향 4 %" or "종단경사 +1.91 %". */
export function slabSlopeLabel(slab: AbutSlab, wingSlopePct?: number): string {
if (slab.slope === 'down4') return `하향 ${SLAB_FALL_PCT} %`;
if (slab.form === 'buried') return `하향 ${SLAB_FALL_PCT} %`;
if (wingSlopePct === undefined) return '종단경사';
return `종단경사 ${wingSlopePct >= 0 ? '+' : ''}${wingSlopePct.toFixed(2)} %`;
}
+3 -1
View File
@@ -144,7 +144,9 @@ export function checkStoredAbutments(list: unknown): void {
}
if (a.slab !== undefined) {
for (const [key, v] of Object.entries(a.slab)) {
const ok = key === 'form' ? v === 'buried' || v === 'exposed' : key === 'slope' ? v === 'down4' || v === 'grade' : finite(v);
// 'slope' was stored by an earlier version (2026-11-04 오전) and is ignored now: the form sets the slope.
const x: unknown = v;
const ok = key === 'form' ? x === 'buried' || x === 'exposed' : key === 'slope' ? x === 'down4' || x === 'grade' : finite(x);
if (!ok) throw new Error(`${who}: 접속슬래브 값 ${key}이(가) 맞지 않습니다.`);
}
}