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:
1 parent
4b8b80ae97
commit
9835fb42b3
7 files changed
+157
-199
No files matched your search
+44
-50
@@ -24,11 +24,10 @@ import {
|
||||
setWingForm,
|
||||
applySlabEdit,
|
||||
setSlabForm,
|
||||
setSlabSlope,
|
||||
SLAB_EDIT_FIELDS,
|
||||
} from './core/abut/edit';
|
||||
import { ABUT_SLAB_DEFAULT, resolveSlab, SLAB_FORM_LABEL, slabDefaultSlope, slabGradePct, slabSection, slabSlopeLabel, type SlabForm, type SlabSlope } from './core/abut/slab';
|
||||
import { abutSlabJointSvg, abutSlabPlanSvg, abutSlabSectionSvg } from './view/abut-slab-svg';
|
||||
import { ABUT_SLAB_DEFAULT, resolveSlab, SLAB_FORM_LABEL, slabGradePct, slabSection, slabSlopeLabel, type SlabForm } from './core/abut/slab';
|
||||
import { abutSlabPlanSvg, abutSlabSectionSvg } from './view/abut-slab-svg';
|
||||
import { ABUT_SHAPE_DEFAULT, footingEnds, normalizeShape, pileRowsMm, resolveShape, wallThicknessMm, type AbutShape } from './core/abut/shape';
|
||||
import { abutFootingPlanSvg } from './view/abut-footing-svg';
|
||||
import { abutWallElevationSvg, abutWallSectionSvg, type AbutWallDrawing } from './view/abut-wall-svg';
|
||||
@@ -1190,21 +1189,17 @@ function fillAbutWingCard(
|
||||
|
||||
/**
|
||||
* 접속슬래브 타일 그림 (2026-11-04, 48 × 24, 사용자 삽도 기준): 오른쪽이 흉벽 · 브라켓 단차, 왼쪽으로 슬래브가 뻗는다.
|
||||
* 매립형 = 흉벽 상단 아래 일정 두께, 노출형 = 상면이 흉벽 상단과 같고 끝에 헌치. 경사는 하향(왼쪽이 낮음) / 종단경사(높음).
|
||||
* 매립형 = 흉벽 상단 아래 일정 두께, 하향 4 % (왼쪽이 낮음). 노출형 = 상면이 흉벽 상단과 같고 끝에 헌치, 종단경사 (왼쪽이 높음).
|
||||
*/
|
||||
const SLAB_ICON = {
|
||||
section(form: SlabForm, slope: SlabSlope): string {
|
||||
section(form: SlabForm): string {
|
||||
const wall = '<path d="M31 22V12h3V2h4v20z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>';
|
||||
const fall = slope === 'down4';
|
||||
const slab =
|
||||
form === 'buried'
|
||||
? fall ? 'M4 11L30 8v4L4 15z' : 'M4 6L30 8v4L4 10z'
|
||||
: fall ? 'M4 4.5L30 2v10L23 6.5L4 8.5z' : 'M4 0.8L30 2v10L23 5.5L4 4.8z';
|
||||
const slab = form === 'buried' ? 'M4 11L30 8v4L4 15z' : 'M4 0.8L30 2v10L23 5.5L4 4.8z';
|
||||
return `${wall}<path d="${slab}" fill="currentColor" fill-opacity="0.18" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/>`;
|
||||
},
|
||||
};
|
||||
|
||||
/** ② 접속슬래브 카드 (시안, 2026-11-04): 형식 · 경사 타일 → 삽도 3개 → 입력 → 자동 값 → 메모. */
|
||||
/** ② 접속슬래브 카드 (시안, 2026-11-04): 형식 타일 (경사는 노트) → 평면 · 단면(접합 상세 합침) → 입력 → 자동 값 → 메모. */
|
||||
function fillAbutSlabCard(
|
||||
box: HTMLDivElement,
|
||||
rec: AbutRecord,
|
||||
@@ -1215,13 +1210,24 @@ function fillAbutSlabCard(
|
||||
): void {
|
||||
const slab = resolveSlab(rec.slab);
|
||||
const say = (err: unknown) => ($<HTMLElement>('abut-panel-plan-msg').textContent = (err as Error).message);
|
||||
const tiles = el('div', undefined, 'abut-tile-row');
|
||||
tiles.append(
|
||||
let model: AbutModel | null = null;
|
||||
let members: AbutMembers | null = null;
|
||||
let wings: WingSolid[] = [];
|
||||
try {
|
||||
model = buildAbut(toAbutInput(rec));
|
||||
members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm }, rec.shape);
|
||||
wings = buildWings(model, members, rec.wings, rec.wingDims);
|
||||
} catch {
|
||||
// shown below the tiles
|
||||
}
|
||||
// 형식 (경사는 형식이 정한다, 2026-11-04): 매립형 = 하향 4 %, 노출형 = 종단경사 따라.
|
||||
const grade = slabSlopeLabel(resolveSlab({ form: 'exposed' }), model?.wing?.slopePct);
|
||||
box.append(
|
||||
optionTiles(
|
||||
'형식',
|
||||
(['buried', 'exposed'] as const).map((f) => ({
|
||||
label: SLAB_FORM_LABEL[f],
|
||||
icon: SLAB_ICON.section(f, slabDefaultSlope(f)),
|
||||
icon: SLAB_ICON.section(f),
|
||||
on: slab.form === f,
|
||||
isDefault: f === 'buried',
|
||||
pick: () => {
|
||||
@@ -1233,29 +1239,10 @@ function fillAbutSlabCard(
|
||||
}
|
||||
},
|
||||
})),
|
||||
),
|
||||
optionTiles(
|
||||
'경사',
|
||||
(['down4', 'grade'] as const).map((s) => ({
|
||||
label: s === 'down4' ? '하향 4 %' : '종단경사 따라',
|
||||
icon: SLAB_ICON.section(slab.form, s),
|
||||
on: slab.slope === s,
|
||||
isDefault: s === slabDefaultSlope(slab.form),
|
||||
pick: () => {
|
||||
if (slab.slope !== s) apply(setSlabSlope(rec, s));
|
||||
},
|
||||
})),
|
||||
`경사 · 매립형 = 하향 4 %, 노출형 = ${grade.replace('종단경사', '종단경사 따라')}`,
|
||||
),
|
||||
);
|
||||
box.append(tiles);
|
||||
let model: AbutModel;
|
||||
let members: AbutMembers;
|
||||
let wings: WingSolid[];
|
||||
try {
|
||||
model = buildAbut(toAbutInput(rec));
|
||||
members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm }, rec.shape);
|
||||
wings = buildWings(model, members, rec.wings, rec.wingDims);
|
||||
} catch {
|
||||
if (!model || !members) {
|
||||
box.append(el('p', '교대 모델을 만들 수 없어 접속슬래브를 그리지 않습니다.', 'muted small'));
|
||||
return;
|
||||
}
|
||||
@@ -1269,7 +1256,7 @@ function fillAbutSlabCard(
|
||||
// 단면은 교대축 가운데의 흉벽 상단에서 잰다.
|
||||
const parapetTopMid = (members.parapetTopStartEl + members.parapetTopEndEl) / 2;
|
||||
const sh = members.shape;
|
||||
const figs = el('div', undefined, 'abut-wall-figs abut-wing-figs');
|
||||
const figs = el('div', undefined, 'abut-wall-figs abut-slab-figs');
|
||||
const addFig = (svg: string, caption: string) => {
|
||||
const fig = el('figure', undefined, 'abut-fig');
|
||||
fig.innerHTML = svg;
|
||||
@@ -1300,20 +1287,26 @@ function fillAbutSlabCard(
|
||||
parapetGapMm: slab.parapetGapMm,
|
||||
haunchLengthMm: slab.haunchLengthMm,
|
||||
}),
|
||||
`단면 · 교대축 직각 (교대축 가운데) · ${SLAB_FORM_LABEL[slab.form]} · 경사 ${slopeText} · 축척 같음`,
|
||||
);
|
||||
addFig(
|
||||
abutSlabJointSvg({
|
||||
form: slab.form,
|
||||
gradePct,
|
||||
parapetTopEl: parapetTopMid,
|
||||
bracketHeightM: sh.bracketHeightM,
|
||||
parapetGapMm: slab.parapetGapMm,
|
||||
thicknessMm: slab.thicknessMm,
|
||||
}),
|
||||
'흉벽–접속슬래브 접합 상세 · 모식도 (부속재는 표시용)',
|
||||
`단면 · 접합 상세 · 교대축 직각 (교대축 가운데) · ${SLAB_FORM_LABEL[slab.form]} · 경사 ${slopeText} · 축척 같음, 중간 생략 · 간격 · 고무판은 키워 그림, 부속재는 표시용`,
|
||||
);
|
||||
box.append(figs);
|
||||
// 흉벽 높이 = 브라켓 상면 ~ 흉벽 상단 (2026-11-04): 벽체·흉벽 카드의 "브라켓 높이"와 같은 값.
|
||||
{
|
||||
const id = idOf('slab-parapet');
|
||||
const lab = el('label', '흉벽 높이 · 브라켓 상면 ~ 흉벽 상단 (mm)');
|
||||
lab.htmlFor = id;
|
||||
const now = Math.round(sh.bracketHeightM * 1000);
|
||||
const input = numberInput(id, String(now));
|
||||
if (now !== ABUT_SHAPE_DEFAULT.bracketHeightMm) input.classList.add('changed-input');
|
||||
input.addEventListener('change', () => {
|
||||
try {
|
||||
apply(applyShapeEdit(rec, 'bracketHeightMm', Number(input.value)));
|
||||
} catch (err) {
|
||||
fail(err, input, String(now));
|
||||
}
|
||||
});
|
||||
box.append(lab, input, el('span', `기본 ${ABUT_SHAPE_DEFAULT.bracketHeightMm} · = 벽체·흉벽 카드 브라켓 높이`, 'muted small'));
|
||||
}
|
||||
for (const f of SLAB_EDIT_FIELDS) {
|
||||
const id = idOf(`slab-${f.key}`);
|
||||
const lab = el('label', f.label);
|
||||
@@ -1335,12 +1328,13 @@ function fillAbutSlabCard(
|
||||
const side = (mm: number, w: WingSolid | undefined) => (w ? (mm > 0 ? `${mm} × ${mm}` : '없음') : '날개벽 없음');
|
||||
auto('모따기 (자동)', `좌 ${side(chamfer.left, leftWing)} · 우 ${side(chamfer.right, rightWing)}`, '= 그 측 날개벽 헌치');
|
||||
auto('폭 (자동)', `${widthM.toFixed(3)} m`, '도면 11.670 · QA-6');
|
||||
auto('경사', slopeText, slab.slope === slabDefaultSlope(slab.form) ? `${SLAB_FORM_LABEL[slab.form]} 기본` : `변경 (${SLAB_FORM_LABEL[slab.form]} 기본 = ${slabDefaultSlope(slab.form) === 'down4' ? '하향 4 %' : '종단경사'})`);
|
||||
const bearing = Math.round((sh.parapetBackM - sh.wallBackM) * 1000) - slab.parapetGapMm;
|
||||
auto('브라켓 얹힘 길이 (자동)', `${bearing} mm`, '= 브라켓 폭 − 흉벽과 간격');
|
||||
if (slab.form === 'exposed') auto('끝 두께 (자동)', `${Math.round(sh.bracketHeightM * 1000)} mm`, '= 흉벽 상단 − 브라켓 상면');
|
||||
box.append(
|
||||
el(
|
||||
'p',
|
||||
'시안 메모 · 매립형은 브라켓 상면에 얹혀 두께가 일정하고, 벽체 배면 뒤에서 경사를 따릅니다. 노출형(2026-11-04 삽도 기준, QA-8 가정)은 상면이 흉벽 상단과 같고 끝은 브라켓 위에 얹히며, 벽체 배면부터 헌치 길이 동안 두께가 끝 두께에서 슬래브 두께로 줄어듭니다. 형식을 바꾸면 경사가 그 형식의 기본값으로 바뀝니다. 접합 부속재(고무판, 타르페이퍼, 다웰, 강관)는 표시용이고, 배근은 철근 단계에서 정합니다(노출형 배근 규칙 미정).',
|
||||
'시안 메모 · 매립형은 브라켓 상면에 얹혀 두께가 일정하고, 벽체 배면 뒤에서 경사를 따릅니다. 노출형(2026-11-04 삽도 기준, QA-8 가정)은 상면이 흉벽 상단과 같고 끝은 브라켓 위에 얹히며, 벽체 배면부터 헌치 길이 동안 두께가 끝 두께에서 슬래브 두께로 줄어듭니다. 경사는 형식이 정합니다(매립형 하향 4 %, 노출형 종단경사). 흉벽 높이는 벽체·흉벽 카드의 브라켓 높이와 같은 값입니다. 접합 부속재(고무판, 타르페이퍼, 다웰, 강관)는 표시용이고, 배근은 철근 단계에서 정합니다(노출형 배근 규칙 미정).',
|
||||
'muted small abut-memo',
|
||||
),
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user