- A 열 is one row across the footing width; each has its own count along the length. Default floor((L − 1400) / 2250) + 1 per 열, as in spec §4.6. - Counts are validated: one per 열, whole numbers 2–30. Spacing below the 2250 mm minimum is shown as a warning, not refused (the plan's 6본 → 5@1800 example). - PierInput gains an optional pileRows; absent means the defaults, so existing files and the P1 results are unchanged. - Change tracking compares arrays by value; 열별 본수 can be reverted and copied (copy item off by default). - The footing card has −/+ per 열 with spacing, total, and the display string. - Tests: defaults 4열 × 5본 = 20 and the 1단계 text; 6본 → 5@1800; limits; model-level 21 piles. - Not verified in a browser. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
90 lines
3.7 KiB
TypeScript
90 lines
3.7 KiB
TypeScript
/**
|
||
* 말뚝 열별 본수 (spec §4.6 · §5.4 · plan C4). A 열 is one row across the footing width; 본 are along the length.
|
||
* Default: each 열 gets floor((길이 − 2·단부)/간격) + 1 본, so the centre spacing is at least the minimum.
|
||
* The user may change any 열's count. Counts below the minimum spacing are allowed and reported as a warning.
|
||
*/
|
||
export interface PileGeometry {
|
||
footingLength: number;
|
||
footingWidth: number;
|
||
/** 단부 거리 (700 mm). */
|
||
end: number;
|
||
/** 최소 중심간격 (2250 mm). */
|
||
minSpacing: number;
|
||
}
|
||
|
||
/** A count outside this range is refused: fewer than two piles has no spacing, and the cap guards against typing errors. */
|
||
export const PILE_COUNT_LIMITS = { min: 2, max: 30 } as const;
|
||
|
||
/** Largest count along one length with centres at or above the minimum spacing. */
|
||
export function maxCount(length: number, g: PileGeometry): number {
|
||
return Math.floor((length - 2 * g.end) / g.minSpacing) + 1;
|
||
}
|
||
|
||
/** Number of 열 across the width. */
|
||
export function rowsAcross(g: PileGeometry): number {
|
||
return maxCount(g.footingWidth, { ...g, minSpacing: g.minSpacing });
|
||
}
|
||
|
||
export function defaultRowCounts(g: PileGeometry): number[] {
|
||
return Array.from({ length: rowsAcross(g) }, () => maxCount(g.footingLength, g));
|
||
}
|
||
|
||
/** Counts to use for a footing: the requested 열별 본수 if any, otherwise the defaults. Refuses bad input. */
|
||
export function resolveRowCounts(requested: number[] | undefined, g: PileGeometry): number[] {
|
||
if (requested === undefined) return defaultRowCounts(g);
|
||
const rows = rowsAcross(g);
|
||
if (requested.length !== rows) {
|
||
throw new Error(`열별 본수는 ${rows}개여야 합니다 (입력 ${requested.length}개).`);
|
||
}
|
||
for (const c of requested) {
|
||
if (!Number.isInteger(c) || c < PILE_COUNT_LIMITS.min || c > PILE_COUNT_LIMITS.max) {
|
||
throw new Error(`본수는 ${PILE_COUNT_LIMITS.min}–${PILE_COUNT_LIMITS.max} 사이의 정수여야 합니다 (입력 ${c}).`);
|
||
}
|
||
}
|
||
return [...requested];
|
||
}
|
||
|
||
export interface RowLayout {
|
||
count: number;
|
||
/** Centre spacing along the length, in m. */
|
||
spacing: number;
|
||
/** True when the spacing is below the minimum (warning, not refused). */
|
||
belowMinimum: boolean;
|
||
}
|
||
|
||
export function rowLayout(counts: number[], g: PileGeometry): RowLayout[] {
|
||
return counts.map((count) => {
|
||
const spacing = (g.footingLength - 2 * g.end) / (count - 1);
|
||
return { count, spacing, belowMinimum: spacing < g.minSpacing - 1e-9 };
|
||
});
|
||
}
|
||
|
||
/** Total piles (sum of the counts along each 열). */
|
||
export function totalPiles(counts: number[]): number {
|
||
return counts.reduce((sum, c) => sum + c, 0);
|
||
}
|
||
|
||
/** Changes one 열's count by delta, staying inside the limits. Returns a new array. */
|
||
export function adjustRow(counts: number[], row: number, delta: number): number[] {
|
||
const next = [...counts];
|
||
next[row] = Math.min(PILE_COUNT_LIMITS.max, Math.max(PILE_COUNT_LIMITS.min, next[row] + delta));
|
||
return next;
|
||
}
|
||
|
||
/**
|
||
* Display string. Uniform counts keep the 1단계 form, e.g. "4@2250 × 3@2250 = 20본"
|
||
* (gaps along the length × gaps across the width). Non-uniform counts list each 열, e.g. "열1 5@2250 · 열2 6@1800 = 23본".
|
||
*/
|
||
export function layoutText(counts: number[], g: PileGeometry): string {
|
||
const layout = rowLayout(counts, g);
|
||
const total = totalPiles(counts);
|
||
const mm = (v: number) => Math.round(v * 1000);
|
||
const across = (g.footingWidth - 2 * g.end) / (counts.length - 1);
|
||
const uniform = counts.every((c) => c === counts[0]);
|
||
if (uniform) {
|
||
return `${layout[0].count - 1}@${mm(layout[0].spacing)} × ${counts.length - 1}@${mm(across)} = ${total}본`;
|
||
}
|
||
const parts = layout.map((r, j) => `열${j + 1} ${r.count - 1}@${mm(r.spacing)}`);
|
||
return `${parts.join(' · ')} = ${total}본`;
|
||
}
|