- 현장타설 기둥·말뚝 형상(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>
94 lines
6.0 KiB
TypeScript
94 lines
6.0 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildPier } from '../src/core/geometry';
|
|
import { buildPierScene } from '../src/view/scene';
|
|
import { buildRebar3D } from '../src/view/rebar3d';
|
|
import { rebarSummary } from '../src/core/rebar/summary';
|
|
import { buildQuantity, quantityRows } from '../src/core/rebar/quantity';
|
|
import { DEFAULT_MATERIAL } from '../src/core/rebar/common';
|
|
import { castSettingsIssues, type CastInPlaceRebar } from '../src/core/rebar/cast-in-place';
|
|
import { castRebarLayout } from '../src/core/rebar/cast-layout';
|
|
import { parseProject, serializeProject, SCHEMA_VERSION, type ProjectFile } from '../src/core/project';
|
|
import { changedKeys } from '../src/core/changes';
|
|
import { validateInput } from '../src/core/validate';
|
|
import type { Issue, PierInput } from '../src/core/types';
|
|
import { loadP1 } from './helpers';
|
|
|
|
const settings: CastInPlaceRebar = { column: { mainDiameterMm: 32, mainCount: 46, coverMm: 100, mainGradeMpa: 500 }, pile: { mainDiameterMm: 32, mainCount: 46, cageLengthMm: 12000, tieKind: 'spiral' }, connection: { mode: 'continuous' } };
|
|
function project(input: PierInput): ProjectFile {
|
|
return { schemaVersion: SCHEMA_VERSION, project: { name: '', bridge: '', client: '', author: '', crs: '', memo: '' }, source: { fileName: '', sheets: [] }, history: [], piers: [{ id: 'one', input, defaults: { ...input, castInPlaceRebar: undefined }, draft: { ...input, castInPlaceRebar: settings } }] };
|
|
}
|
|
|
|
describe('cast-in-place independent rendering and incomplete quantities', () => {
|
|
for (const [type, columnCount] of [['T형', 1], ['2주형', 2], ['3주형', 3]] as const) {
|
|
for (const foundationType of ['직접기초', '말뚝기초', '현장타설말뚝'] as const) {
|
|
it(`${type} / ${foundationType}: concrete, rebar and summary remain usable`, () => {
|
|
const m = buildPier({ ...loadP1(), type, columnCount, foundationType });
|
|
const concrete = buildPierScene(m);
|
|
expect(concrete.parts.coping.children.length).toBeGreaterThan(0);
|
|
const rebar = buildRebar3D(m);
|
|
const result = rebarSummary(m);
|
|
expect(rebar.byName.get('C1')!.length).toBeGreaterThan(0);
|
|
const q = buildQuantity(result.rows, DEFAULT_MATERIAL, result.incomplete);
|
|
if (foundationType === '현장타설말뚝') {
|
|
expect(m.footings).toHaveLength(0);
|
|
expect(m.piles).toHaveLength(columnCount);
|
|
m.columns.forEach((c, i) => expect(c.bottom).toBe(m.piles[i].top));
|
|
expect(rebar.byName.get('기둥-P1')!.length).toBeGreaterThan(0);
|
|
expect(rebar.byName.get('말뚝-P1')!.length).toBeGreaterThan(0);
|
|
expect(rebar.byName.get('기둥-P3')!.length).toBeGreaterThan(0);
|
|
expect(rebar.byName.get('말뚝-P3')!.length).toBeGreaterThan(0);
|
|
expect(rebar.byName.has('B1')).toBe(false);
|
|
expect(result.rows.some(r => r.member === 'column')).toBe(true);
|
|
expect(result.rows.some(r => r.member === 'pile')).toBe(true);
|
|
expect(result.rows.some(r => r.member === 'footing')).toBe(false);
|
|
expect(q.missing).toEqual(result.incomplete);
|
|
expect(q.missing).toEqual(expect.arrayContaining(castRebarLayout(m).missing));
|
|
expect(q.missing).toContain('말뚝 저부 P6 검토안 미적용');
|
|
expect(quantityRows(q).filter(r => r[0] === '미계산 · 합계 미완료')).toHaveLength(q.missing.length);
|
|
expect(q.kg).toBeGreaterThan(0);
|
|
} else {
|
|
expect(rebar.byName.get('P1')!.length).toBeGreaterThan(0);
|
|
if (columnCount === 1) expect(result.incomplete).toEqual([]);
|
|
else expect(result.incomplete).toContain('다주형 철근 번호 대응·cycle별 이음 위치·수평철근 갈고리 상세 확인 필요');
|
|
expect(q.missing).toEqual(result.incomplete);
|
|
}
|
|
rebar.group.traverse(o => { const mesh = o as import('three').Mesh; mesh.geometry?.dispose(); });
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
describe('cast-in-place storage and validation', () => {
|
|
it('round-trips dedicated settings in generated/default/draft inputs without merging legacy settings', () => {
|
|
const input = { ...loadP1(), foundationType: '현장타설말뚝' as const, castInPlaceRebar: settings };
|
|
const p = project(input);
|
|
expect(parseProject(serializeProject(p))).toEqual(p);
|
|
expect(changedKeys({ ...input, castInPlaceRebar: undefined }, input)).toContain('castInPlaceRebar');
|
|
});
|
|
it('legacy projects keep absent settings absent rather than inventing defaults', () => {
|
|
const p = project(loadP1());
|
|
delete p.piers[0].draft;
|
|
expect(parseProject(serializeProject(p)).piers[0].input.castInPlaceRebar).toBeUndefined();
|
|
});
|
|
for (const bad of [null, [], { pile: [] }, { column: { mainCount: 2.5 } }, { pile: { coverMm: -1 } }, { connection: { mode: ['continuous'] } }, { column: { tieKind: 'unknown' } }, { column: { mainCount: '46' } }, { pile: { extra: 1 } }]) {
|
|
it(`rejects malformed settings ${JSON.stringify(bad)} in saved draft`, () => {
|
|
const p = project(loadP1());
|
|
p.piers[0].draft!.castInPlaceRebar = bad as CastInPlaceRebar;
|
|
expect(() => parseProject(serializeProject(p))).toThrow('현장타설');
|
|
});
|
|
}
|
|
it('geometry length changes produce a warning without destroying concrete or preventing reopening', () => {
|
|
const input = { ...loadP1(), foundationType: '현장타설말뚝' as const, pileLengthMm: 10000, castInPlaceRebar: settings };
|
|
const issues: Issue[] = [];
|
|
expect(validateInput(input, issues)).toBe(true);
|
|
expect(issues.some(i => i.code === 'CIP01')).toBe(true);
|
|
expect(() => parseProject(serializeProject(project(input)))).not.toThrow();
|
|
});
|
|
it('has no numeric defaults and checks cage length and cover geometry', () => {
|
|
expect(castSettingsIssues({})).toEqual([]);
|
|
expect(castSettingsIssues({ pile: { cageLengthMm: 15000 } }, 10000)).toHaveLength(1);
|
|
expect(castSettingsIssues({ column: { coverMm: 1000 } }, 10000, 2000)).toHaveLength(1);
|
|
expect(castSettingsIssues({ pile: { mainCount: Infinity } })).toHaveLength(1);
|
|
});
|
|
});
|