feat(core): 일체기초 footings for 2·3주형 with per-footing pile grid

- Columns closer than one footing length (10400) share a 일체기초 whose
  length is the outer-column distance + 10400, centred between them (spec §4.6).
- Pile grid is computed from each footing's length (rows across 8150,
  pile count along the length from (length − 1400) / 2250 + 1).
- Blinding concrete follows each combined footing.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-22 09:01:40 +09:00
1 parent 5466b3522e
commit ad59c0c74d
3 files changed
+64 -22

No files matched your search

+40 -21
View File
@@ -240,21 +240,23 @@ export function buildPier(input: PierInput): PierModel {
const pileLength = input.pileLengthMm / 1000;
const pileTip = input.footingBottomEl - pileLength;
for (const s of colS) {
if (input.foundationType === '현장타설말뚝') {
if (input.foundationType === '현장타설말뚝') {
for (const s of colS) {
piles.push({ s, n: 0, top: input.footingBottomEl, bottom: pileTip, diameter: DIM.columnDiameter });
continue;
}
footings.push({ s, top: footingTop, bottom: input.footingBottomEl, length: DIM.footingL, width: DIM.footingW });
blindings.push({
s,
top: input.footingBottomEl,
length: DIM.footingL + DIM.blindingMargin,
width: DIM.footingW + DIM.blindingMargin,
thickness: DIM.blindingT,
});
if (input.foundationType === '말뚝기초') {
piles.push(...pilesFor(s, input.footingBottomEl, pileLength));
} else {
for (const g of footingGroups(colS)) {
footings.push({ s: g.s, top: footingTop, bottom: input.footingBottomEl, length: g.length, width: DIM.footingW });
blindings.push({
s: g.s,
top: input.footingBottomEl,
length: g.length + DIM.blindingMargin,
width: DIM.footingW + DIM.blindingMargin,
thickness: DIM.blindingT,
});
if (input.foundationType === '말뚝기초') {
piles.push(...pilesFor(g.s, input.footingBottomEl, pileLength, g.length));
}
}
}
@@ -306,7 +308,7 @@ export function buildPier(input: PierInput): PierModel {
columnHeight: levelBottom - columns[0].bottom,
soilCover: input.groundEl - (input.foundationType === '현장타설말뚝' ? input.footingBottomEl : footingTop),
pileCount: piles.length,
pileLayout: input.foundationType === '말뚝기초' ? pileLayoutText() : null,
pileLayout: input.foundationType === '말뚝기초' ? pileLayoutText(footings[0].length) : null,
pileTipEl: piles.length ? pileTip : null,
blockHeightFront,
blockHeightBack,
@@ -326,30 +328,47 @@ function segmentTop(segments: Segment[], s: number): number {
return hit.top;
}
/**
* Footings (spec §4.6). Adjacent columns closer than one footing length share a 일체기초 whose length is
* the distance between its outer columns + DIM.footingL, centred between them. Other columns get their own footing.
*/
export function footingGroups(colS: number[]): { s: number; length: number }[] {
const groups: number[][] = [];
for (const s of [...colS].sort((a, b) => a - b)) {
const last = groups[groups.length - 1];
if (last && s - last[last.length - 1] < DIM.footingL) last.push(s);
else groups.push([s]);
}
return groups.map((g) => ({
s: (g[0] + g[g.length - 1]) / 2,
length: g[g.length - 1] - g[0] + DIM.footingL,
}));
}
/**
* Piles under one footing (spec §4.6): rows across the footing width, piles along the length,
* both with an end distance of 0.7 m and an even spacing that never exceeds 2.25 m.
*/
export function pileGrid(): { countS: number; countN: number; spacingS: number; spacingN: number } {
export function pileGrid(footingLength: number = DIM.footingL): { countS: number; countN: number; spacingS: number; spacingN: number } {
const span = (size: number) => Math.floor((size - 2 * DIM.pileEnd) / DIM.pileSpacing) + 1;
const countS = span(DIM.footingL);
const countS = span(footingLength);
const countN = span(DIM.footingW);
return {
countS,
countN,
spacingS: (DIM.footingL - 2 * DIM.pileEnd) / (countS - 1),
spacingS: (footingLength - 2 * DIM.pileEnd) / (countS - 1),
spacingN: (DIM.footingW - 2 * DIM.pileEnd) / (countN - 1),
};
}
/** Display string such as "4@2250 × 3@2250 = 20본" (gaps along the length × gaps across the width). */
export function pileLayoutText(): string {
const g = pileGrid();
export function pileLayoutText(footingLength: number = DIM.footingL): string {
const g = pileGrid(footingLength);
return `${g.countS - 1}@${Math.round(g.spacingS * 1000)} × ${g.countN - 1}@${Math.round(g.spacingN * 1000)} = ${g.countS * g.countN}본`;
}
export function pilesFor(s: number, topEl: number, length: number): PileModel[] {
const { countS, countN, spacingS, spacingN } = pileGrid();
export function pilesFor(s: number, topEl: number, length: number, footingLength: number = DIM.footingL): PileModel[] {
const { countS, countN, spacingS, spacingN } = pileGrid(footingLength);
const out: PileModel[] = [];
for (let i = 0; i < countS; i++) {
for (let j = 0; j < countN; j++) {
-1
View File
@@ -8,7 +8,6 @@ export const NOT_IMPLEMENTED_RULES = [
'E04 fck 미입력 (배근 단계, 3단계)',
'W06 속성 복사 건너뜀 (2단계)',
'W08 솔플레이트 T1−T2 검사 (2단계)',
'일체기초 판정 (2·3주형)',
] as const;
/** Input-level checks (spec §10 E01, E02). Returns false when the pier must not be built. */
+24
View File
@@ -106,3 +106,27 @@ describe('bearing and block checks (spec §10 W03, W04)', () => {
expect(issues.map((i) => i.code)).toContain('W04');
});
});
describe('일체기초 (spec §4.6)', () => {
it('2주형 L 12000: columns 7200 apart share one footing of 17600 with 4 × 8 piles', () => {
const m = buildPier({ ...loadP1(), type: '2주형', columnCount: 2, copingLengthMm: 12000, foundationType: '말뚝기초' });
expect(m.footings).toHaveLength(1);
expect(m.footings[0].length).toBeCloseTo(17.6, 6);
expect(m.footings[0].s).toBeCloseTo(6.0, 6);
expect(m.blindings[0].length).toBeCloseTo(17.8, 6);
expect(m.piles).toHaveLength(32);
expect(m.derived.pileLayout).toBe('7@2314 × 3@2250 = 32본');
});
it('3주형 L 12000: three columns 3600 apart share one footing', () => {
const m = buildPier({ ...loadP1(), type: '3주형', columnCount: 3, copingLengthMm: 12000, foundationType: '직접기초' });
expect(m.footings).toHaveLength(1);
expect(m.footings[0].length).toBeCloseTo(17.6, 6);
});
it('2주형 with columns 18000 apart: separate footings of 10400', () => {
const m = buildPier({ ...loadP1(), type: '2주형', columnCount: 2, copingLengthMm: 30000, foundationType: '직접기초' });
expect(m.footings).toHaveLength(2);
expect(m.footings.every((f) => Math.abs(f.length - 10.4) < 1e-9)).toBe(true);
});
});