- The station input is removed. The section shows the 단부 (first shear station, from the 3D bar records) and the first column centre, side by side. - The 상면 간격 label is removed. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
105 lines
4.7 KiB
TypeScript
105 lines
4.7 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
||
import { buildPier } from '../src/core/geometry';
|
||
import { buildRebar3D } from '../src/view/rebar3d';
|
||
import { chainDims, copingBarGroup, copingEndStation, copingFrontSvg, copingSectionSvg } from '../src/view/coping-rebar-svg';
|
||
import { loadP1 } from './helpers';
|
||
|
||
const model = buildPier(loadP1());
|
||
const scene = buildRebar3D(model);
|
||
|
||
describe('코핑 배근 정면 · 단면 (승인 2026-10-26)', () => {
|
||
it('the 3D bar records include the coping bars, the same data as the meshes', () => {
|
||
const coping = scene.records.filter((r) => /^C\d/.test(r.name));
|
||
expect(coping.length).toBeGreaterThan(0);
|
||
expect(scene.records.length).toBeGreaterThan(coping.length - 1);
|
||
});
|
||
|
||
it('the front view draws one polyline per coping bar', () => {
|
||
const coping = scene.records.filter((r) => /^C\d/.test(r.name));
|
||
const svg = copingFrontSvg(scene.records, model.copingL * 1000);
|
||
expect((svg.match(/<polyline/g) ?? []).length).toBe(coping.length);
|
||
});
|
||
|
||
it('groups the bars: C1–C8 main, C9–C11 loops, C12–C13 sides, C14–C16 steps', () => {
|
||
expect(copingBarGroup('C1')).toBe('main');
|
||
expect(copingBarGroup('C9')).toBe('loop');
|
||
expect(copingBarGroup('C12')).toBe('side');
|
||
expect(copingBarGroup('C15')).toBe('step');
|
||
});
|
||
|
||
it('the section at mid-length shows the bars that cross it', () => {
|
||
const svg = copingSectionSvg(scene.records, model.copingL / 2);
|
||
expect((svg.match(/<circle/g) ?? []).length).toBeGreaterThan(0);
|
||
});
|
||
});
|
||
|
||
describe('단면 · 폐합 글자 겹침 (승인 2026-10-26)', () => {
|
||
it('writes each bubble label once', () => {
|
||
const svg = copingSectionSvg(scene.records, 6, model.copingRefTop, model.copingB * 1000);
|
||
// Only the top row and the side rows are shown here: their bubbles, once each.
|
||
for (const label of ['C1–6', 'C7·8', 'C12·13']) {
|
||
expect(svg.split(`>${label}<`).length - 1).toBe(1);
|
||
}
|
||
for (const label of ['C10', 'C14', 'C16']) {
|
||
expect(svg).not.toContain(`>${label}<`);
|
||
}
|
||
expect(svg).not.toContain('상면 간격');
|
||
});
|
||
});
|
||
|
||
describe('정면 번호 풍선과 치수 (승인 2026-10-26, 시안 ③)', () => {
|
||
const svg = copingFrontSvg(scene.records, model.copingL * 1000);
|
||
it('has one bubble per bar group, with its label', () => {
|
||
for (const label of ['C1–6', 'C12·13', 'C9', 'C10', 'C11', 'C7·8', 'C14', 'C15', 'C16']) {
|
||
expect(svg).toContain(`>${label}<`);
|
||
}
|
||
});
|
||
it('writes the 시안 length dimension (300 · 4500 · 2400 · 4500 · 300 = 12000)', () => {
|
||
expect(svg).toContain('18@250=4500');
|
||
expect(svg).toContain('8@300=2400');
|
||
expect(svg).toContain('코핑 길이 12000');
|
||
});
|
||
});
|
||
|
||
describe('정면 두 번째 치수선과 범례 (승인 2026-10-26, 시안)', () => {
|
||
const svg = copingFrontSvg(scene.records, model.copingL * 1000);
|
||
it('writes the column-centre span 10@1000=10000 and the legend', () => {
|
||
expect(svg).toContain('10@1000=10000');
|
||
expect(svg).toContain('기둥 중심');
|
||
expect(svg).toContain('주철근 (C1–C8)');
|
||
expect(svg).toContain('전단·띠 (C9–C11)');
|
||
});
|
||
});
|
||
|
||
describe('정면 치수는 3D 철근 위치에서 계산 (승인 2026-10-26)', () => {
|
||
it('the chains come from the bar positions and match the 시안 values', () => {
|
||
const svg = copingFrontSvg(scene.records, model.copingL * 1000, model.columns[0].s);
|
||
const texts = [...svg.matchAll(/font-size="220"[^>]*>([^<]*)</g)].map((x) => x[1]);
|
||
expect(texts).toEqual(['300', '18@250=4500', '8@300=2400', '18@250=4500', '300', '900', '10@1000=10000', '900']);
|
||
});
|
||
});
|
||
|
||
describe('chainDims', () => {
|
||
it('groups repeated gaps and writes a single gap as its length', () => {
|
||
expect(chainDims([300, 550, 800, 1100], 0, 1400).map((d) => d.text)).toEqual(['300', '2@250=500', '2@300=600']);
|
||
});
|
||
});
|
||
|
||
describe('아래 치수선의 0 = 기둥 중심 (승인 2026-10-26)', () => {
|
||
it('the column-centre tick sits at the middle of the 10@1000 span (the 10 intervals are centred on the column)', () => {
|
||
const col = model.columns[0].s * 1000;
|
||
const svg = copingFrontSvg(scene.records, model.copingL * 1000, model.columns[0].s);
|
||
expect(svg).toContain(`<line x1="${col}" y1=`);
|
||
const seg = [...svg.matchAll(/<line x1="([-\d.]+)" y1="[-\d.]+" x2="([-\d.]+)" y2="[-\d.]+" stroke="#18212B" stroke-width="10"/g)].map((m) => [Number(m[1]), Number(m[2])]);
|
||
const span = seg.find(([a, b]) => b - a > 9000 && b - a < 11000);
|
||
expect(span).toBeDefined();
|
||
expect((span![0] + span![1]) / 2).toBeCloseTo(col, 0);
|
||
});
|
||
});
|
||
|
||
describe('단부 위치 (승인 2026-10-26)', () => {
|
||
it('the 단부 section is at the first shear station, 0.3 m from the coping start for P1', () => {
|
||
expect(copingEndStation(scene.records)).toBeCloseTo(0.3, 6);
|
||
});
|
||
});
|