feat(abut): ② 확대기초 · 말뚝 카드를 시안대로 작성 (평면 삽도, 버림, 열별 본수)
- 선택: 평면 skew 평행사변형(기본) / 직사각(비활성, 다음 단계), 말뚝 원기둥 D600 / 강관 · PHC(비활성, 규칙 미정) - 평면 삽도 (src/view/abut-footing-svg.ts): 저판, 벽체 윤곽, 교대축, 말뚝 3열(skew만큼 밀림), 열 위치 · 본수, 폭 치수 - 입력: 폭 4600, 두께 1100, 앞굽 1200 (뒷굽 자동 1000), 버림 여유 · 두께 100 / 100, 말뚝 지름 600 (QA-5), 연단 650, 본수 규칙 간격 2000 - 열 위치 "650 · 2000 · 1300 · 650" 입력 (전면 연단 자동, 합 = 폭 검사), 열별 말뚝 본수 표 − / + (0 = 본수 규칙) - 자동 값: 뒷굽, 길이(교좌면 길이), 말뚝 길이(①에서 수정), 선단 EL, 본수 규칙 문구. 시안 메모 - 3D: 교대 버림을 저판 아래에 그림 (부재 표시 "버림" 토글 적용), 말뚝 지름 입력 반영, 말뚝 열을 skew만큼 s 방향으로 밀어 배치 - 예전 저장 파일의 앞굽 끝 · 뒷굽 끝은 폭 · 앞굽으로 바꿔 읽음 - 테스트 추가 · 갱신. 전체 661건 통과, 타입 검사 · 빌드 통과. 브라우저 시각 확인은 미실시 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
55b679932e
commit
372d759eb7
10 files changed
+425
-46
No files matched your search
@@ -0,0 +1,59 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import * as XLSX from 'xlsx';
|
||||
import * as THREE from 'three';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { buildAbut } from '../src/core/abut/geometry';
|
||||
import { parseAbutments, toAbutInput } from '../src/core/abut/parse';
|
||||
import { buildMembers } from '../src/core/abut/members';
|
||||
import { buildWings } from '../src/core/abut/wings';
|
||||
import { abutSkewK, buildAbutGroup } from '../src/view/abut-scene';
|
||||
import { abutFootingPlanSvg } from '../src/view/abut-footing-svg';
|
||||
|
||||
const RAG = fileURLToPath(new URL('../../../RAG/운곡교P1입력.xlsx', import.meta.url));
|
||||
const rec = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abutments[0];
|
||||
const input = toAbutInput(rec);
|
||||
const model = buildAbut(input);
|
||||
const group = (changes = {}) => {
|
||||
const m = buildMembers(model, { bottomEl: input.foundationBottomEl, pileLengthMm: input.pileLengthMm }, changes);
|
||||
return { m, g: buildAbutGroup(model, m, buildWings(model, m)) };
|
||||
};
|
||||
|
||||
describe('교대 확대기초 · 말뚝 (시안 ②, 3D)', () => {
|
||||
it('draws the 버림 under the 저판: 100 thick, 100 wider on each side', () => {
|
||||
const { m, g } = group();
|
||||
const blind = new THREE.Box3().setFromObject(g.getObjectByName('blinding') as THREE.Object3D);
|
||||
const foot = new THREE.Box3().setFromObject(g.getObjectByName('footing') as THREE.Object3D);
|
||||
expect(blind.max.y).toBeCloseTo(m.footingBottomEl!, 4); // float32 vertices
|
||||
expect(blind.max.y - blind.min.y).toBeCloseTo(0.1, 4);
|
||||
expect(foot.min.z - blind.min.z).toBeCloseTo(0.1, 4);
|
||||
expect(blind.max.z - foot.max.z).toBeCloseTo(0.1, 4);
|
||||
});
|
||||
|
||||
it('no 버림 when its thickness is 0', () => {
|
||||
expect(group({ blindingThicknessMm: 0 }).g.getObjectByName('blinding')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('the piles follow the 지름 input and shift with the skew row by row', () => {
|
||||
const { m, g } = group({ pileDiameterMm: 800 });
|
||||
const piles = (g.getObjectByName('piles') as THREE.Group).children;
|
||||
expect(piles).toHaveLength(18);
|
||||
const box = new THREE.Box3().setFromObject(piles[0]);
|
||||
expect(box.max.x - box.min.x).toBeCloseTo(0.8, 2);
|
||||
const k = abutSkewK(model);
|
||||
const row = m.piles[0];
|
||||
expect((box.min.x + box.max.x) / 2).toBeCloseTo(row.s[0] + k * row.t, 6);
|
||||
});
|
||||
|
||||
it('평면 삽도: the rows, their t and counts, and the width line', () => {
|
||||
const { m } = group();
|
||||
const svg = abutFootingPlanSvg({
|
||||
lengthM: model.length, skewK: abutSkewK(model), footingFrontM: m.shape.footingFrontM, footingBackM: m.shape.footingBackM,
|
||||
wallFrontM: m.shape.wallFrontM, wallBackM: m.shape.wallBackM,
|
||||
rows: m.piles.map((r, i) => ({ t: r.t, s: r.s, name: ['3열 · 배면', '2열', '1열 · 전면'][i] })),
|
||||
pileDiameterM: m.shape.pileDiameterM, rowDimsMm: [650, 2000, 1300, 650], footingWidthMm: 4600,
|
||||
});
|
||||
for (const s of ['1열 · 전면 · t +2150 · 6본', '2열 · t +850 · 6본', '3열 · 배면 · t −1150 · 6본', '4600 = 뒷굽쪽 650 · 2000 · 1300 · 650 전면', '교대축']) expect(svg).toContain(s);
|
||||
expect(svg.match(/<circle /g)).toHaveLength(18);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { ABUT_SHAPE_DEFAULT, normalizeShape, resolveShape, shapeIssues, wallThicknessMm, type AbutShape } from '../src/core/abut/shape';
|
||||
import { ABUT_SHAPE_DEFAULT, footingEnds, normalizeShape, pileRowsMm, resolveShape, shapeIssues, wallThicknessMm, type AbutShape } from '../src/core/abut/shape';
|
||||
|
||||
describe('교대 형상 입력 (시안 ② 벽체·흉벽 · 확대기초·말뚝)', () => {
|
||||
it('the defaults equal the spec values (wall +1.6 … −0.8, footing +2.8 … −1.8, piles 650 / 2000)', () => {
|
||||
@@ -22,8 +22,10 @@ describe('교대 형상 입력 (시안 ② 벽체·흉벽 · 확대기초·말
|
||||
});
|
||||
|
||||
it('refuses a footing that does not cover the wall, and non-positive sizes', () => {
|
||||
expect(shapeIssues({ footingBackMm: -500 }).join(' ')).toContain('뒷굽');
|
||||
expect(shapeIssues({ footingFrontMm: 1000 }).join(' ')).toContain('앞굽');
|
||||
expect(shapeIssues({ footingWidthMm: 3300 }).join(' ')).toContain('뒷굽'); // 3300 − 1200 − 2400 < 0
|
||||
expect(shapeIssues({ footingToeMm: 0 }).join(' ')).toContain('앞굽');
|
||||
expect(shapeIssues({ pileRowGap2Mm: 2000 }).join(' ')).toContain('열 위치'); // 650 + 2000 + 2000 ≥ 4600 − 0
|
||||
expect(shapeIssues({ pileCountRow1: 1.5 }).join(' ')).toContain('열별 말뚝 본수');
|
||||
expect(shapeIssues({ bracketWidthMm: -1 }).join(' ')).toContain('브라켓 폭');
|
||||
expect(shapeIssues({ seatStepIgnoreMm: -1 }).join(' ')).toContain('인접 무시 기준');
|
||||
expect(shapeIssues({ pileSpacingMm: 0 }).join(' ')).toContain('말뚝');
|
||||
@@ -44,8 +46,10 @@ const rec = parseAbutments(XLSX.read(readFileSync(RAG), { type: 'buffer' })).abu
|
||||
describe('형상 입력이 계산에 반영됨 (시안 ② 카드)', () => {
|
||||
it('the card has one field per shape value, in the mockup order', () => {
|
||||
expect(SHAPE_EDIT_FIELDS.map((f) => f.key)[0]).toBe('wallFrontMm');
|
||||
expect(SHAPE_EDIT_FIELDS).toHaveLength(14);
|
||||
expect(SHAPE_EDIT_FIELDS.filter((f) => f.group === 'footing').map((f) => f.key)).toEqual(['footingThicknessMm', 'footingFrontMm', 'footingBackMm', 'pileEdgeMm', 'pileSpacingMm']);
|
||||
expect(SHAPE_EDIT_FIELDS).toHaveLength(17);
|
||||
expect(SHAPE_EDIT_FIELDS.filter((f) => f.group === 'footing').map((f) => f.key)).toEqual([
|
||||
'footingWidthMm', 'footingThicknessMm', 'footingToeMm', 'blindingMarginMm', 'blindingThicknessMm', 'pileDiameterMm', 'pileEdgeMm', 'pileSpacingMm',
|
||||
]);
|
||||
});
|
||||
|
||||
it('the new drawing values do not change the geometry (the model ignores them)', () => {
|
||||
@@ -59,7 +63,7 @@ describe('형상 입력이 계산에 반영됨 (시안 ② 카드)', () => {
|
||||
});
|
||||
|
||||
it('a footing that does not cover the wall is refused by the edit rule', () => {
|
||||
expect(() => applyShapeEdit(rec, 'footingBackMm', -500)).toThrow('뒷굽');
|
||||
expect(() => applyShapeEdit(rec, 'footingWidthMm', 3300)).toThrow('뒷굽');
|
||||
expect(rec.shape).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -83,4 +87,19 @@ describe('형상 입력이 계산에 반영됨 (시안 ② 카드)', () => {
|
||||
expect(buildMembers(model, foundation).wallSegments).toHaveLength(5);
|
||||
expect(buildMembers(model, foundation, { seatStepIgnoreMm: 100 }).wallSegments).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('저판: 폭 4600 · 앞굽 1200 → 뒷굽 1000 (자동), the ends +2.8 / −1.8; old 앞굽 끝 · 뒷굽 끝 files open the same', () => {
|
||||
expect(footingEnds(ABUT_SHAPE_DEFAULT)).toEqual({ frontMm: 2800, backMm: -1800, heelMm: 1000 });
|
||||
expect(normalizeShape({ footingFrontMm: 3000, footingBackMm: -2000 } as Partial<AbutShape>)).toEqual({ footingToeMm: 1400, footingWidthMm: 5000 });
|
||||
});
|
||||
|
||||
it('열 위치 650 · 2000 · 1300 · (650) gives the rows −1.15 / +0.85 / +2.15; 열별 본수 overrides the rule', () => {
|
||||
expect(pileRowsMm(ABUT_SHAPE_DEFAULT)).toEqual({ back: -1150, middle: 850, front: 2150, frontEdgeMm: 650 });
|
||||
const model = buildAbut({ name: 'A1', type: 'A1', seatEl: rec.seatEl, ip1: rec.ip1, ip2: rec.ip2, ip3: rec.ip3, bearings: rec.bearings.map((b) => ({ no: b.no, pos: b.pos, z: b.z, seatEl: b.seatEl })) });
|
||||
const rule = buildMembers(model, {});
|
||||
expect(rule.piles.map((r) => r.s.length)).toEqual([6, 6, 6]);
|
||||
const more = buildMembers(model, {}, { pileCountRow1: 7 });
|
||||
expect(more.piles.map((r) => r.s.length)).toEqual([6, 6, 7]); // 3열 배면, 2열, 1열 전면
|
||||
expect(more.piles[2].t).toBeCloseTo(2.15, 9);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user