- 현장타설 기둥·말뚝 형상(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>
107 lines
5.8 KiB
TypeScript
107 lines
5.8 KiB
TypeScript
import type { PierInput } from '../types';
|
|
import { DEFAULT_COLUMN_REBAR } from './column-settings';
|
|
import { NOMINAL_DIAMETER, DEFAULT_MATERIAL, type Material } from './common';
|
|
|
|
/** Dedicated settings: round-column defaults were authorised by the user on 2026-11-05. */
|
|
export interface CastRebarPart {
|
|
mainDiameterMm?: number;
|
|
mainCount?: number;
|
|
mainGradeMpa?: number;
|
|
coverMm?: number;
|
|
concreteStrengthMpa?: number;
|
|
tieDiameterMm?: number;
|
|
tieGradeMpa?: number;
|
|
tieSpacingMm?: number;
|
|
endZoneLengthMm?: number;
|
|
endZoneSpacingMm?: number;
|
|
cageLengthMm?: number;
|
|
endCoverMm?: number;
|
|
topEmbedmentMm?: number;
|
|
tieKind?: 'hoop' | 'spiral';
|
|
coverReference?: 'clear' | 'main-center';
|
|
innerMainDiameterMm?: number;
|
|
innerMainCount?: number;
|
|
}
|
|
|
|
export interface CastInPlaceRebar {
|
|
column?: CastRebarPart;
|
|
pile?: CastRebarPart;
|
|
connection?: {
|
|
mode?: 'continuous' | 'lap' | 'coupler';
|
|
lapLengthMm?: number;
|
|
couplerLengthMm?: number;
|
|
};
|
|
}
|
|
|
|
export const CAST_PART_FIELDS: { key: Exclude<keyof CastRebarPart, 'tieKind' | 'coverReference'>; label: string; only?: 'column' | 'pile' }[] = [
|
|
{ key: 'mainDiameterMm', label: '외측 주철근 공칭지름 (mm)' },
|
|
{ key: 'mainCount', label: '외측 주철근 본수 (본)' },
|
|
{ key: 'mainGradeMpa', label: '주철근 강도 fy (MPa)' },
|
|
{ key: 'coverMm', label: '측면 피복 (아래 기준 선택, mm)' },
|
|
{ key: 'concreteStrengthMpa', label: '콘크리트 강도 fck (MPa)' },
|
|
{ key: 'tieDiameterMm', label: '띠·나선철근 직경 (mm)' },
|
|
{ key: 'tieGradeMpa', label: '띠·나선철근 강도 fy (MPa)' },
|
|
{ key: 'tieSpacingMm', label: '일반구간 띠 간격·나선 피치 (mm)' },
|
|
{ key: 'endZoneLengthMm', label: '단부 보강구간 길이 (mm)' },
|
|
{ key: 'endZoneSpacingMm', label: '단부 보강구간 간격 (mm)' },
|
|
{ key: 'cageLengthMm', label: '말뚝 철근망 길이 (mm)', only: 'pile' },
|
|
{ key: 'endCoverMm', label: '말뚝 선단 순피복 (mm)', only: 'pile' },
|
|
{ key: 'topEmbedmentMm', label: '코핑 내부 주철근 연장 길이 (mm)', only: 'column' },
|
|
{ key: 'innerMainDiameterMm', label: '내측 주철근 공칭지름 (mm)' },
|
|
{ key: 'innerMainCount', label: '내측 주철근 본수 (본)' },
|
|
];
|
|
|
|
export function castRebarDefaults(input: Pick<PierInput, 'columnRebar' | 'castInPlaceRebar'>, material: Material = DEFAULT_MATERIAL): CastInPlaceRebar {
|
|
const c = { ...DEFAULT_COLUMN_REBAR, ...input.columnRebar };
|
|
const saved = input.castInPlaceRebar ?? {};
|
|
const part = (key: 'column' | 'pile'): CastRebarPart => ({
|
|
mainDiameterMm: NOMINAL_DIAMETER.S32, mainCount: c.nDiv, mainGradeMpa: material.grade.S,
|
|
innerMainDiameterMm: NOMINAL_DIAMETER.S32, innerMainCount: c.nDiv,
|
|
coverMm: c.c, coverReference: saved[key]?.coverMm !== undefined && saved[key]?.coverReference === undefined ? 'clear' : 'main-center',
|
|
concreteStrengthMpa: material.fck,
|
|
tieDiameterMm: NOMINAL_DIAMETER.H19, tieGradeMpa: material.grade.H, tieSpacingMm: c.ctcP3,
|
|
endZoneSpacingMm: c.ctcP5, tieKind: 'hoop',
|
|
...(key === 'pile' ? { endCoverMm: 250 } : {}),
|
|
...saved[key],
|
|
});
|
|
return { column: part('column'), pile: part('pile'), connection: { mode: 'coupler', couplerLengthMm: 150, ...saved.connection } };
|
|
}
|
|
|
|
export const CAST_PENDING = ['현장타설 기둥 배근 상세 미확정', '현장타설말뚝 배근 상세 미확정', '기둥·말뚝 접합부 배근 상세 미확정'];
|
|
|
|
/** Storage and input validation; domain-specific spacing/anchorage rules remain unconfirmed. */
|
|
export function castSettingsIssues(value: unknown, pileLengthMm?: number, diameterMm?: number): string[] {
|
|
if (value === undefined) return [];
|
|
const issues: string[] = [];
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return ['현장타설 배근 입력 형식이 맞지 않습니다.'];
|
|
const record = value as Record<string, unknown>;
|
|
if (Object.keys(record).some(k => !['column', 'pile', 'connection'].includes(k))) issues.push('알 수 없는 현장타설 배근 항목입니다.');
|
|
for (const part of ['column', 'pile', 'connection'] as const) {
|
|
const values = record[part];
|
|
if (values === undefined) continue;
|
|
if (!values || typeof values !== 'object' || Array.isArray(values)) { issues.push(`${part}: 입력 형식이 맞지 않습니다.`); continue; }
|
|
for (const [key, v] of Object.entries(values)) {
|
|
if (v === undefined) continue;
|
|
if (part === 'connection' && key === 'mode') {
|
|
if (typeof v !== 'string' || !['continuous', 'lap', 'coupler'].includes(v)) issues.push('접합 방식이 맞지 않습니다.');
|
|
} else if (part !== 'connection' && key === 'tieKind') {
|
|
if (typeof v !== 'string' || !['hoop', 'spiral'].includes(v)) issues.push('횡철근 형식이 맞지 않습니다.');
|
|
} else if (part !== 'connection' && key === 'coverReference') {
|
|
if (typeof v !== 'string' || !['clear', 'main-center'].includes(v)) issues.push('피복 기준이 맞지 않습니다.');
|
|
} else {
|
|
const allowed = part === 'connection' ? ['lapLengthMm', 'couplerLengthMm'] : CAST_PART_FIELDS.filter(f => !f.only || f.only === part).map(f => f.key);
|
|
if (!allowed.includes(key) || typeof v !== 'number' || !Number.isFinite(v) || v <= 0 || (key.endsWith('Count') && !Number.isInteger(v))) issues.push(`${part}.${key}: 양수${key.endsWith('Count') ? ' 정수' : ''}로 입력하세요.`);
|
|
}
|
|
}
|
|
if (part === 'pile') {
|
|
const p = values as CastRebarPart;
|
|
if (pileLengthMm !== undefined && p.cageLengthMm !== undefined && p.cageLengthMm > pileLengthMm) issues.push('말뚝 철근망 길이가 말뚝 길이보다 큽니다.');
|
|
}
|
|
if (part !== 'connection') {
|
|
const p = values as CastRebarPart;
|
|
if (diameterMm !== undefined && p.coverMm !== undefined && p.coverMm * 2 >= diameterMm) issues.push(`${part}: 피복이 단면 반지름 이상입니다.`);
|
|
}
|
|
}
|
|
return issues;
|
|
}
|