feat(rebar): per-member fck on ② for column, coping and footing

Each member can take its own fck on its ② card. A member without one uses the ③ 공통 fck, and fy stays common.
The rebar table and the 3D model both read the resolved value. The I02 note stays until every fck member has its
own value. The project file stores the values as optional fckByMember and refuses a bad value on open.
Changing the fck is not a shape change, so it is not counted in the pending changes.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-23 08:53:39 +09:00
1 parent d8fd6adcf0
commit 7cf5835689
6 files changed
+175 -18

No files matched your search

+18 -9
View File
@@ -4,7 +4,7 @@ import { bottomPiece, topBarLength, widthLayout } from './coping-main';
import { shearBars, shearBarsMulti, copingSideBars, SIDE_HOOK_LEG, c14Bars, c14Length, stairBars, stepDevLength, undersideAt, shearULength, capLength, PIECES_PER_POSITION } from './coping';
import { columnRebar, COLUMN_P1 } from './column';
import { bottomBars, crossBars, sideBars, assemblyBars, shearB9 } from './footing';
import { DEFAULT_MATERIAL, isAssumedMaterial, Material, splitPieces } from './common';
import { DEFAULT_MATERIAL, FckByMember, isAssumedMaterial, Material, materialFor, splitPieces } from './common';
/** 근거 badge (spec §6.1): where the value comes from. */
export type Basis = 'input' | 'default' | 'calc' | 'assumed';
@@ -113,11 +113,20 @@ function footingBarList(Lf: number, Wf: number, Hf: number, columnX: number[]):
type RawRow = Omit<RebarRow, 'spacing' | 'actual' | 'position' | 'direction' | 'shape' | 'basis'>;
export function rebarSummary(model: PierModel, m: Material = DEFAULT_MATERIAL): { rows: RebarRow[]; warnings: RebarWarning[] } {
export function rebarSummary(
model: PierModel,
common: Material = DEFAULT_MATERIAL,
byMember: FckByMember = {},
): { rows: RebarRow[]; warnings: RebarWarning[] } {
const rows: RawRow[] = [];
const warnings: RebarWarning[] = [];
if (isAssumedMaterial(m)) {
warnings.push({ code: 'I02', message: `fck ${m.fck} MPa · fy ${m.fyH} / ${m.fyS} MPa는 가정값입니다. 정착·이음 길이에 반영됩니다. 공통 설정에서 확인하세요.`, target: 'common' });
const mCoping = materialFor(common, byMember, 'coping');
const mColumn = materialFor(common, byMember, 'column');
const mFooting = materialFor(common, byMember, 'footing');
// The I02 note stays while the common values are the assumed ones, unless every fck-bearing member has its own fck.
const ownFck = (['column', 'coping', 'footing'] as const).every((k) => byMember[k] !== undefined);
if (isAssumedMaterial(common) && !ownFck) {
warnings.push({ code: 'I02', message: `fck ${common.fck} MPa · fy ${common.fyH} / ${common.fyS} MPa는 가정값입니다. 정착·이음 길이에 반영됩니다. 공통 설정에서 확인하세요.`, target: 'common' });
}
const L = model.copingL * 1000;
const B = model.copingB * 1000;
@@ -130,7 +139,7 @@ export function rebarSummary(model: PierModel, m: Material = DEFAULT_MATERIAL):
const emb1 = embedments('S1');
const emb2 = embedments('S2');
const range = (xs: number[]) => `${Math.round(Math.min(...xs))}–${Math.round(Math.max(...xs))}`;
const anchorage = stepDevLength(m); // RB-S04: the embedment must reach the H19 development length
const anchorage = stepDevLength(mCoping); // RB-S04: the embedment must reach the H19 development length
const shortest = Math.min(...emb1, ...emb2);
if (shortest < anchorage) {
warnings.push({ code: 'RB-S04', message: `받침블럭 철근 묻힘 ${Math.round(shortest)} mm < 정착길이 ${anchorage} mm (코핑 안 묻힘 부족)`, target: 'bearing' });
@@ -145,7 +154,7 @@ export function rebarSummary(model: PierModel, m: Material = DEFAULT_MATERIAL):
for (const [name, k] of [['C1', 1], ['C2', 2], ['C3', 3], ['C4', 1], ['C5', 2], ['C6', 3]] as const) {
rows.push({ member: 'coping', name, size: 'H29', count: perRow, length: topBarLength(L, k), note: '상면, 이음 무시' });
}
const bottom = bottomPiece(m);
const bottom = bottomPiece(mCoping);
rows.push({ member: 'coping', name: 'C7', size: 'H29', count: perRow, length: bottom.piece, note: '하면, 2조각 겹침이음' });
rows.push({ member: 'coping', name: 'C8', size: 'H29', count: perRow, length: bottom.piece, note: '하면, 2조각 겹침이음' });
const colS = model.columns.map((c) => c.s * 1000);
@@ -182,12 +191,12 @@ export function rebarSummary(model: PierModel, m: Material = DEFAULT_MATERIAL):
const lineCount = (line: number) => c14.filter((b) => b.line === line).length;
const omitted = c14.filter((b) => !b.legs).length;
rows.push({ member: 'coping', name: 'C14', size: 'H19', count: c14.length, length: c14Length(model.copingB * 1000), note: `위 2 줄 ${lineCount(1)} · ${lineCount(2)}본씩, 아래 ${[3, 4, 5, 6].map(lineCount).join(' · ')} (합 ${c14.length}) · 연직 발 생략 ${omitted}개 (하면 밖)` });
const stairs = stairBars(model.copingL * 1000, model.copingB * 1000, model.copingRefTop, m);
const stairs = stairBars(model.copingL * 1000, model.copingB * 1000, model.copingRefTop, mCoping);
rows.push({ member: 'coping', name: 'C15', size: 'H19', count: stairs.c15.length, length: stairs.c15[0].length, note: '계단부, 가장 높은 구간 (거꾸로 U)' });
rows.push({ member: 'coping', name: 'C16', size: 'H19', count: stairs.c16.length, length: stairs.c16[0].length, note: `계단부, 3·4구간 (${Math.round(stairs.c16[0].length)} / ${Math.round(stairs.c16[stairs.c16.length - 1].length)})` });
// Column (기둥철근.md · plan C4), P1 defaults
const col = columnRebar(COLUMN_P1, m);
const col = columnRebar(COLUMN_P1, mColumn);
rows.push({ member: 'column', name: 'P1', size: 'S32', count: col.mainP1, length: null, note: `외측 r ${col.r1}, 마디 ${col.p1Pieces.join(' / ')} (첫 마디에 갈고리 ${col.p1Hook}, 기둥철근.md §3.2)` });
rows.push({ member: 'column', name: 'P2', size: 'S32', count: col.mainP2, length: null, note: `내측 r ${col.r2}, 마디 ${col.p2Pieces.join(' / ')}` });
rows.push({ member: 'column', name: '주철근 커플러', size: 'D32용 (L150 Ø60)', count: col.couplers, length: 150, note: '@3000, P2는 +150 mm 상향 엇갈림' });
@@ -219,7 +228,7 @@ export function rebarSummary(model: PierModel, m: Material = DEFAULT_MATERIAL):
}
}
for (const b of agg.values()) {
const split = splitPieces(b.length, 25.4, m.fyS, m.fck, STANDARD_BAR);
const split = splitPieces(b.length, 25.4, mFooting.fyS, mFooting.fck, STANDARD_BAR);
if (split && b.length > STANDARD_BAR && b.name !== 'B9') {
rows.push({ member: 'footing', name: b.name, size: b.size, count: b.count, length: split.pieceLength, note: `${b.note}, 2조각 이음 (추천안, 겹침 ${Math.round(split.lap)})` });
warnings.push({ code: 'RB-F08', message: `${b.name} 길이 ${Math.round(b.length)} mm > 정척 ${STANDARD_BAR} mm: 2조각 이음을 추천안으로 적용 (Q-B6).`, target: 'footing', bar: b.name });