feat(rebar): 현장타설 말뚝 · 다주형 코핑 형상과 철근 계산

- 현장타설 기둥·말뚝 형상(cast-in-place-shape)과 주철근·횡철근·커플러 계산(rebar/cast-in-place, cast-layout)
- 다주형 코핑 철근(multicol-coping), 기둥 높이 · 양단부 연장
- 철근표 요약 · 재료 수량 · 3D 뷰 연결과 삽도(cast-rebar-svg)
- 검사 기준 갱신과 회귀 테스트, 검증 스크립트 보관

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-11-06 13:15:01 +09:00
1 parent 3922b330d5
commit 2e0bfb6fc8
29 files changed
+1750 -71

No files matched your search

+16 -10
View File
@@ -1,13 +1,14 @@
import { buildAxis, copingSpan, fromAxis, kinkArc, nearKink, toAxis } from './axis';
import { buildIntegratedBlocks, type IntegratedBlock } from './integrated-block';
import { type BlockRebarSettings, DEFAULT_BLOCK_REBAR } from './rebar/bearing-block';
import { type CopingRebarSettings, DEFAULT_COPING_REBAR, DEFAULT_TOP_SPLICE, type TopSpliceSettings } from './rebar/coping-settings';
import { type CopingRebarSettings, copingDefaults, DEFAULT_TOP_SPLICE, type TopSpliceSettings } from './rebar/coping-settings';
import { type ColumnRebarSettings, DEFAULT_COLUMN_REBAR } from './rebar/column-settings';
import { type FootingRebarSettings, DEFAULT_FOOTING_REBAR } from './rebar/footing-settings';
import { layoutText, resolveRowCounts, rowLayout, type PileGeometry } from './piles';
import type { PierInput, Row, SolplatePlan } from './types';
import { SOLPLATE_RULE_DEFAULT, solplatePlan } from './solplate-rule';
import { type CopingGeom, P1_COPING_GEOM } from './coping-geom';
import { castShape } from './cast-in-place-shape';
/** Dimensions in m. Sources: intent.PR sheet (P1 rules) and spec.md §3 defaults. */
export const DIM = {
@@ -15,7 +16,7 @@ export const DIM = {
copingH1: 1.0,
copingH2: 1.5,
levelWidth: 2.1,
/** Multi-column coping: underside between columns sits this far below Level, with 45° haunches of the same size (spec §4.5, Q3 추천안). */
/** Multi-column coping: underside between columns sits this far above Level, with 45° haunches of the same size (spec §4.4, Q3). */
haunch: 0.3,
padThickness: 0.05,
padSize: 1.0,
@@ -207,6 +208,8 @@ export interface PierModel {
blockHeightBack: number;
frontBackDiffSpread: number;
};
/** Dedicated rebar source; never substitute footing-anchored column inputs. */
castInput?: PierInput;
}
/**
@@ -343,7 +346,7 @@ export function buildPier(input: PierInput): PierModel {
const levelEnd = Math.max(...colG) + levelW / 2;
if (levelStart < 0 || levelEnd > L) throw new Error('코핑 길이가 Level 구간보다 짧습니다.');
// Underside from the right end to the left end. Between columns (2·3주형) it drops 300 below Level with 300 × 300 haunches (spec §4.5, Q3).
// Underside from the right end to the left end. Between columns (2·3주형) it rises 300 above Level with 300 × 300 haunches (spec §4.4, Q3).
const underside: [number, number][] = [[L, endBottom]];
const levelZones = [...colG].sort((a, b) => b - a).map((s) => [s - levelW / 2, s + levelW / 2] as const);
levelZones.forEach(([zoneStart, zoneEnd], k) => {
@@ -352,8 +355,8 @@ export function buildPier(input: PierInput): PierModel {
if (!next) return;
const gap = zoneStart - next[1];
if (gap <= 2 * DIM.haunch) throw new Error('기둥 사이 간격이 헌치(300 × 2)보다 좁아 코핑 하면을 만들 수 없습니다.');
const lowerEl = levelBottom - DIM.haunch;
underside.push([zoneStart - DIM.haunch, lowerEl], [next[1] + DIM.haunch, lowerEl], [next[1], levelBottom]);
const raisedEl = levelBottom + DIM.haunch;
underside.push([zoneStart - DIM.haunch, raisedEl], [next[1] + DIM.haunch, raisedEl], [next[1], levelBottom]);
});
// Elevation outline in coping coordinates for one row of tops, closed by the shared underside.
@@ -394,7 +397,9 @@ export function buildPier(input: PierInput): PierModel {
}
}
const footingTop = input.foundationType === '현장타설말뚝' ? input.footingBottomEl : input.footingBottomEl + fd.H;
const cast = castShape(input);
if (input.foundationType === '현장타설말뚝' && (!Number.isFinite(cast.pileTopEl) || cast.pileTopEl >= levelBottom || !Number.isFinite(input.pileLengthMm) || input.pileLengthMm <= 0)) throw new Error('현장타설말뚝은 지반고가 코핑 Level 하면보다 낮고 말뚝 길이가 양수여야 합니다.');
const footingTop = input.foundationType === '현장타설말뚝' ? cast.pileTopEl : input.footingBottomEl + fd.H;
const columns: ColumnModel[] = colG.map((g, i) => ({
s: g - base[colK[i]],
k: colK[i],
@@ -407,11 +412,11 @@ export function buildPier(input: PierInput): PierModel {
const blindings: BlindingModel[] = [];
const piles: PileModel[] = [];
const pileLength = input.pileLengthMm / 1000;
const pileTip = input.footingBottomEl - pileLength;
const pileTip = input.foundationType === '현장타설말뚝' ? cast.pileBottomEl : input.footingBottomEl - pileLength;
if (input.foundationType === '현장타설말뚝') {
columns.forEach((c) => {
piles.push({ s: c.s, k: c.k, n: 0, top: input.footingBottomEl, bottom: pileTip, diameter: colD });
piles.push({ s: c.s, k: c.k, n: 0, top: cast.pileTopEl, bottom: pileTip, diameter: colD });
});
} else {
for (const f of footingSets) {
@@ -487,7 +492,7 @@ export function buildPier(input: PierInput): PierModel {
blocks,
integratedBlocks: buildIntegratedBlocks(blocks, input.bearingLayout === '일체'),
blockRebar: { ...DEFAULT_BLOCK_REBAR, ...input.blockRebar },
copingRebar: { ...DEFAULT_COPING_REBAR, ...input.copingRebar },
copingRebar: { ...copingDefaults(columns.length), ...input.copingRebar },
topSplice: { ...DEFAULT_TOP_SPLICE, ...input.topSplice },
columnRebar: { ...DEFAULT_COLUMN_REBAR, ...input.columnRebar },
footingRebar: { ...DEFAULT_FOOTING_REBAR, ...input.footingRebar },
@@ -498,7 +503,7 @@ export function buildPier(input: PierInput): PierModel {
groundEl: input.groundEl,
derived: {
columnHeight: levelBottom - columns[0].bottom,
soilCover: input.groundEl - (input.foundationType === '현장타설말뚝' ? input.footingBottomEl : footingTop),
soilCover: input.foundationType === '현장타설말뚝' ? 0 : input.groundEl - footingTop,
pileCount: piles.length,
pileLayout: input.foundationType === '말뚝기초' ? layoutText(resolveRowCounts(input.pileRows, pileGeometry(footings[0].length, fd.W)), pileGeometry(footings[0].length, fd.W)) : null,
pileTipEl: piles.length ? pileTip : null,
@@ -506,6 +511,7 @@ export function buildPier(input: PierInput): PierModel {
blockHeightBack,
frontBackDiffSpread,
},
...(input.foundationType === '현장타설말뚝' ? { castInput: { ...input } } : {}),
};
}