feat(abut-rebar): ③ 교대 받침블럭 카드
- 보기 탭 평면 · 단면, 입력 두 칸 (교대축 = S2, 교대축 직각 = S1, 사용자 결정 2026-11-05), 피복 상면 · 측면, 첫 철근 여유, 다리 길이 - 자동 값: 블럭 콘크리트 높이 (블럭 − 패드 50), 묻힘 S2 · S1, 적용 블럭 수. 실간격 묶음 수정 S1 · S2 행 - 경고 RB-AK01 (S1 다리 ↔ W2 층, Q-S4 경고만) · 받침블럭 member 목록 활성화 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a68e4a0366
commit
bc91a8166f
1 file changed
+79
-4
+79
-4
@@ -31,9 +31,12 @@ import {
|
||||
applyParapetRebar,
|
||||
applyWingRebar,
|
||||
applySlabRebar,
|
||||
applyBlockRebar,
|
||||
} from './core/abut/edit';
|
||||
import { ABUT_FOOTING_REBAR_DEFAULT, abutFootingGeom, abutFootingRebar, ABUT_FOOTING_SIZES, type AbutFootingGeom, type AbutFootingRebar, type AbutFootingRebarSettings } from './core/abut/rebar/footing';
|
||||
import { lenText, skewAngle } from './core/abut/rebar/skew-layout';
|
||||
import { ABUT_BLOCK_PAD_MM, ABUT_BLOCK_REBAR_DEFAULT, abutBlockRebar, type AbutBlockRebar, type AbutBlockSettings } from './core/abut/rebar/block';
|
||||
import { ABUT_BLOCK_COLORS, abutBlockPlanSvg as abutBlockRebarPlanSvg, abutBlockSectionSvg as abutBlockRebarSectionSvg } from './view/abut-block-rebar-svg';
|
||||
import { ABUT_SLAB_REBAR_DEFAULT, ABUT_SLAB_SIZES, abutSlabGeom, abutSlabRebar, type AbutSlabGeom, type AbutSlabRebar, type AbutSlabRebarSettings } from './core/abut/rebar/slab';
|
||||
import { ABUT_SLAB_COLORS, abutSlabPlanSvg as abutSlabRebarPlanSvg, abutSlabSectionSvg as abutSlabRebarSectionSvg } from './view/abut-slab-rebar-svg';
|
||||
import { ABUT_WING_REBAR_DEFAULT, ABUT_WING_SIZES, abutWingGeom, abutWingRebar, type AbutWingGeom, type AbutWingRebar, type AbutWingRebarSettings } from './core/abut/rebar/wing';
|
||||
@@ -234,6 +237,8 @@ const state = {
|
||||
abutWingView: 'front' as 'front' | 'back' | 'corner',
|
||||
/** ③ 교대 접속슬래브 보기 탭 (평면 / 단면). */
|
||||
abutSlabView: 'plan' as 'plan' | 'sec',
|
||||
/** ③ 교대 받침블럭 보기 탭 (평면 / 단면). */
|
||||
abutBlockView: 'plan' as 'plan' | 'sec',
|
||||
abutRebarNotes: false,
|
||||
/** Member card selected on ② (시안 ② 부재 목록). */
|
||||
member: 'coping' as MemberId,
|
||||
@@ -3107,10 +3112,10 @@ function renderCopingDrawings(box: HTMLDivElement, pier: Pier): void {
|
||||
box.appendChild(wrap);
|
||||
}
|
||||
|
||||
/** ③ 교대 부재 (시안 순서, plan_abut AD9). 확대기초 (AD3) · 벽체 (AD4) · 흉벽 (AD5) · 날개벽 (AD7) · 접속슬래브 (AD8) have rules; 받침블럭 (AD6) comes next. */
|
||||
/** ③ 교대 부재 (시안 순서, plan_abut AD9). All six 부재 have rules now (AD3–AD8). */
|
||||
type AbutRebarMemberId = 'blk' | 'wall' | 'para' | 'foot' | 'wing' | 'slab';
|
||||
const ABUT_REBAR_MEMBERS: { id: AbutRebarMemberId; label: string; marks: string; ready: boolean }[] = [
|
||||
{ id: 'blk', label: '받침블럭', marks: 'S1·S2', ready: false },
|
||||
{ id: 'blk', label: '받침블럭', marks: 'S1·S2', ready: true },
|
||||
{ id: 'wall', label: '벽체', marks: 'W1–W11', ready: true },
|
||||
{ id: 'para', label: '흉벽·브라켓', marks: 'P1–P11', ready: true },
|
||||
{ id: 'foot', label: '확대기초', marks: 'B1–B10', ready: true },
|
||||
@@ -3180,6 +3185,18 @@ function abutWingsBuilt(rec: AbutRecord, footing: AbutFootingRebar): { geom: Abu
|
||||
}
|
||||
}
|
||||
|
||||
/** The 받침블럭 rebar of an abutment: one block per bearing (블럭 콘크리트 = 블럭 높이 − 패드 50). */
|
||||
function abutBlockBuilt(rec: AbutRecord): { result: AbutBlockRebar } | null {
|
||||
try {
|
||||
const model = buildAbut(toAbutInput(rec));
|
||||
const members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm }, rec.shape);
|
||||
const blocks = model.bearings.map((b) => ({ no: b.no, s: b.s, t: model.frontSide * b.lateral, rotDeg: b.rotDeg, seatEl: b.seatEl, blockHeightMm: b.blockHeightMm }));
|
||||
return { result: abutBlockRebar(blocks, members.footingBottomEl ?? 0, rec.rebar?.block, resolveWallRebar(rec.rebar?.wall).c, rec.name) };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The 접속슬래브 rebar of an abutment, on the ② slab plan and section (null when it does not build). */
|
||||
function abutSlabBuilt(rec: AbutRecord): { geom: AbutSlabGeom; result: AbutSlabRebar } | null {
|
||||
try {
|
||||
@@ -3211,8 +3228,11 @@ function renderAbutRebar(rec: AbutRecord): void {
|
||||
const para = wall ? abutParapetBuilt(rec, wall) : null;
|
||||
const wings = built ? abutWingsBuilt(rec, built.result) : [];
|
||||
const slabR = abutSlabBuilt(rec);
|
||||
const blockR = abutBlockBuilt(rec);
|
||||
const issuesOf = (id: AbutRebarMemberId) =>
|
||||
(id === 'foot'
|
||||
(id === 'blk'
|
||||
? blockR?.result.issues
|
||||
: id === 'foot'
|
||||
? built?.result.issues
|
||||
: id === 'wall'
|
||||
? wall?.result.issues
|
||||
@@ -3251,7 +3271,7 @@ function renderAbutRebar(rec: AbutRecord): void {
|
||||
if (!member.ready) {
|
||||
$('rebar-note').textContent = `${rec.name} · ${member.label} · Type-1 · ${member.marks} · 다음 단계`;
|
||||
box.append(el('p', `${member.label} 배근은 다음 단계에서 추가합니다 (철근 기획서 확정, plan_abut AD4–AD8).`, 'muted'));
|
||||
} else if (!built || (member.id === 'wall' && !wall) || (member.id === 'para' && !para) || (member.id === 'slab' && !slabR)) {
|
||||
} else if (!built || (member.id === 'blk' && !blockR) || (member.id === 'wall' && !wall) || (member.id === 'para' && !para) || (member.id === 'slab' && !slabR)) {
|
||||
$('rebar-note').textContent = `${rec.name} · 교대 모델을 만들 수 없어 배근하지 않습니다.`;
|
||||
} else if (member.id === 'wall' && wall) {
|
||||
const g = wall.geom;
|
||||
@@ -3259,6 +3279,11 @@ function renderAbutRebar(rec: AbutRecord): void {
|
||||
const high = g.segments.find((sg) => sg.no === g.segments.length)!;
|
||||
$('rebar-note').textContent = `${rec.name} · Type-1 · 두께 ${Math.round(g.xf - g.xb)} · 높이 ${Math.round(low.H)} (구간 1) ~ ${Math.round(high.H)} (구간 ${high.no}) · 교좌면 계단 ${g.segments.length}구간 · 피복 ${wall.result.settings.c}`;
|
||||
fillAbutWallRebarCard(box, rec, wall);
|
||||
} else if (member.id === 'blk' && blockR) {
|
||||
const r = blockR.result;
|
||||
const nBlocks = new Set(r.paths.map((p) => p.block)).size;
|
||||
$('rebar-note').textContent = `${rec.name} · Type-1 · 블럭 ${nBlocks}개 (분리) · 1200 × 1200 × ${r.concreteH + ABUT_BLOCK_PAD_MM} (패드 ${ABUT_BLOCK_PAD_MM} 포함) · 묻힘 S2 ${Math.round(r.embedment.S2)} · S1 ${Math.round(r.embedment.S1)}`;
|
||||
fillAbutBlockRebarCard(box, rec, blockR.result);
|
||||
} else if (member.id === 'slab' && slabR) {
|
||||
const g = slabR.geom;
|
||||
$('rebar-note').textContent = `${rec.name} · Type-1 ${g.form === 'buried' ? '매립형' : '노출형'} · 폭 ${Math.round(g.B)} × 길이 ${Math.round(g.Ls)} · 경사 ${g.gradePct.toFixed(1)} %${g.form === 'exposed' ? ' · 매립형 규칙 적용 (사용자 결정 2026-11-05)' : ''}`;
|
||||
@@ -3586,6 +3611,56 @@ function fillAbutWallRebarCard(box: HTMLDivElement, rec: AbutRecord, built: { ge
|
||||
});
|
||||
}
|
||||
|
||||
/** ③ 교대 받침블럭 카드 (시안, 받침블럭철근.md §8). 입력 두 칸: 교대축 (S2) · 교대축 직각 (S1) — 사용자 결정 2026-11-05. */
|
||||
function fillAbutBlockRebarCard(box: HTMLDivElement, rec: AbutRecord, r: AbutBlockRebar): void {
|
||||
const s = r.settings;
|
||||
const D = ABUT_BLOCK_REBAR_DEFAULT;
|
||||
const C = ABUT_BLOCK_COLORS;
|
||||
const view = state.abutBlockView;
|
||||
const seat = (m: 'S1' | 'S2') => `${Math.round(r.embedment[m])}`;
|
||||
abutRebarCard<AbutBlockSettings>(box, {
|
||||
idPrefix: `abk-${rec.name}`,
|
||||
views: [['plan', '평면'], ['sec', '단면']],
|
||||
view,
|
||||
setView: (id) => (state.abutBlockView = id as 'plan' | 'sec'),
|
||||
svg: (pick) => (view === 'plan' ? abutBlockRebarPlanSvg(r, pick) : abutBlockRebarSectionSvg(r, pick)),
|
||||
caption: view === 'plan' ? '평면 · 블럭 1200 × 1200 (받침 1개) · S2 실선 (위, 교대축 간격) · S1 파선 (아래, 교대축 직각 간격)' : '단면 · 교좌면 기준 · 몰탈패드 50 · S2 위 줄 · S1 아래 줄 · 다리는 교좌면 아래로 묻힘',
|
||||
memo: `블럭 ${r.paths.length ? new Set(r.paths.map((p) => p.block)).size : 0}개 (받침마다 1개, 분리). S1 다리 묻힘 ${seat('S1')} — 벽체 W2 층(교좌면 − 100)을 지나면 경고만 (Q-S4, 확인 후 결정).`,
|
||||
legendHtml: `<span style="color:${C.s1}">━ S1 (주철근, 아래)</span> · <span style="color:${C.s2}">━ S2 (보조, 위)</span>`,
|
||||
baseTitle: '기본간격 · 받침블럭 (부재별 입력, 두 칸)',
|
||||
base: [
|
||||
['교대축 (mm)', s.spacingS2, D.spacingS2, (v) => ({ spacingS2: v })],
|
||||
['교대축 직각 (mm)', s.spacingS1, D.spacingS1, (v) => ({ spacingS1: v })],
|
||||
],
|
||||
inputs: [
|
||||
['상면 피복 (mm)', s.coverTop, D.coverTop, (v) => ({ coverTop: v })],
|
||||
['측면 피복 (mm)', s.coverSide, D.coverSide, (v) => ({ coverSide: v })],
|
||||
['첫 철근 최소 여유 (mm)', s.firstGap, D.firstGap, (v) => ({ firstGap: v })],
|
||||
['S1 · S2 다리 길이 (mm)', s.legLength, D.legLength, (v) => ({ legLength: v })],
|
||||
],
|
||||
autos: [
|
||||
['블럭 콘크리트 높이 (블럭 − 패드 50)', `${r.concreteH} (≥ ${s.coverTop} + 13 → 블럭 안 배근)`],
|
||||
['교좌면 아래 묻힘 S2 · S1', `${seat('S2')} · ${seat('S1')}`],
|
||||
['적용 블럭', `${r.paths.length ? new Set(r.paths.map((p) => p.block)).size : 0}개 · 배치 분리`],
|
||||
],
|
||||
bars: r.bars.map((b) => ({ mark: b.mark, role: b.role, size: b.size, spacing: b.spacing, actual: b.actual, shape: b.shape, length: b.length, count: b.count, position: b.position, note: b.note, basis: b.basis, modified: b.modified, lengthMin: undefined, lengthMax: b.length })),
|
||||
roleColor: { 주철근: C.s1, 보조: C.s2 },
|
||||
overrides: {
|
||||
S1: { key: 'spacingS1', base: s.spacingS1, label: 'S1 교대축 직각 간격' },
|
||||
S2: { key: 'spacingS2', base: s.spacingS2, label: 'S2 교대축 간격' },
|
||||
},
|
||||
basisNote: `근거 · 받침블럭철근.md §6.6 · 규격 S1 · S2 H13 · 교각 받침블럭 규칙 재사용 (깊이만 H13) · 묻힘 S2 ${seat('S2')} / S1 ${seat('S1')}`,
|
||||
set: (patch) => {
|
||||
try {
|
||||
applyAbutRecord(applyBlockRebar(rec, patch));
|
||||
} catch (err) {
|
||||
setError((err as Error).message);
|
||||
renderAll();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** ③ 교대 접속슬래브 카드 (시안, 교대접속슬래브철근.md §9). */
|
||||
function fillAbutSlabRebarCard(box: HTMLDivElement, rec: AbutRecord, built: { geom: AbutSlabGeom; result: AbutSlabRebar }): void {
|
||||
const { geom: g, result: r } = built;
|
||||
|
||||
Reference in new issue
Block a user