v4: 검증 스크립트 추가 - 수량 및 H형강 단면 기준 간섭 검사

부재 수량을 설계 기준과 대조하고, H형강은 플랜지와 웨브로 나눠 간섭을 검사한다.
박스 근사에서 나오던 엄지말뚝-토류판 충돌은 실제 단면 기준으로 해소됨을 확인한다.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude HaikuandClaude Haiku 4.5 committed 2026-10-21 10:47:03 +09:00
1 parent d6abe6707a
commit ef0c01eb67
2 files changed
+101 -1

No files matched your search

+5 -1
View File
@@ -246,8 +246,12 @@ C:\Users\User\.claude\projects\d--Obsidian-Vault-999ETC-261016_HW_DwfTo3D\memory
- 버팀보 X 간격: 도면 치수 "11@5.000" 및 4m 치수 기준 (13지점)
- 벽체 방향 대응: A-A 단면 X ↔ 계획도 X 선형 대응, 말뚝 높은 쪽은 X 큰 쪽
**검증 (2026-10-21)**
- `node src/build/verify-v4.mjs` : 부재 수량 8개 항목 일치, 간섭 없음 (H형강 플랜지·웨브 분리 검사)
- 이전 박스 근사 검사의 엄지말뚝↔토류판 240쌍은 실제 단면 기준으로 겹치지 않음
- 열린 가정 목록은 위 항목 그대로 유지 (도면 확인 후 갱신)
**다음 단계**
- 검증 자동화 (수량, 간섭, 가정 목록)
- 웹앱 이식 (src/web) 및 Vercel 배포
---
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env node
/**
* verify-v4.mjs - v4 통합모델 검증
* 1) 부재 수량: 설계 기준과 일치하는지
* 2) 간섭: H형강은 플랜지 2개 + 웨브로 나눠 검사 (상자 근사보다 정확)
* 실패 항목이 있으면 종료 코드 1
*/
import fs from 'fs';
const EXPECTED = {
'엄지말뚝': 122,
'토류판': 120,
'띠장': 48,
'수직 PIECE': 52,
'버팀보': 26,
'스크류잭': 26,
'채널': 12,
'중간말뚝': 13
};
const d = JSON.parse(fs.readFileSync('out/gasiseol-v4-stats.json', 'utf8'));
const EPS = 1e-3;
let failed = false;
console.log('\n[1] 부재 수량');
const counts = {};
d.parts.forEach(p => { counts[p.memberType] = (counts[p.memberType] || 0) + 1; });
Object.keys({ ...EXPECTED, ...counts }).forEach(type => {
const exp = EXPECTED[type] ?? 0, got = counts[type] ?? 0;
const ok = exp === got;
if (!ok) failed = true;
console.log(` ${ok ? 'OK ' : 'NG '} ${type.padEnd(12)} 기대 ${String(exp).padStart(3)} 실제 ${String(got).padStart(3)}`);
});
// 로컬 좌표(u: 폭, v: 춤, a: 길이) → 월드 좌표 축 매핑 (viewer 와 동일)
// vertical: u→x, v→y, a→z / horizontal(rot y π/2): u→z, v→y, a→x / horizontalY(rot x π/2): u→x, v→z, a→y
function hBoxes(p) {
const { h, w, tf, tw, length: L } = p.dims;
const local = [
[-w / 2, w / 2, h / 2 - tf, h / 2],
[-w / 2, w / 2, -h / 2, -h / 2 + tf],
[-tw / 2, tw / 2, -h / 2 + tf, h / 2 - tf]
];
return local.map(([u0, u1, v0, v1]) => {
const a0 = -L / 2, a1 = L / 2;
if (p.axis === 'horizontal') return { x: [a0, a1], y: [v0, v1], z: [u0, u1] };
if (p.axis === 'horizontalY') return { x: [u0, u1], y: [a0, a1], z: [v0, v1] };
return { x: [u0, u1], y: [v0, v1], z: [a0, a1] };
});
}
function components(p) {
const { x, y, z } = p.position;
const shift = (b) => ({ x: [b.x[0] + x, b.x[1] + x], y: [b.y[0] + y, b.y[1] + y], z: [b.z[0] + z, b.z[1] + z] });
if (p.shape === 'PANEL') {
const { w, t, length: L } = p.dims;
return [shift({ x: [-w / 2, w / 2], y: [-t / 2, t / 2], z: [-L / 2, L / 2] })];
}
if (p.shape === 'JACK') {
return [shift({ x: [-0.15, 0.15], y: [-0.29975, 0.29975], z: [-0.1547, 0.1547] })];
}
if (p.shape === 'CHANNEL') {
const { w, h, length: L } = p.dims;
return [shift({ x: [-L / 2, L / 2], y: [-w / 2, w / 2], z: [-h / 2, h / 2] })];
}
return hBoxes(p).map(shift);
}
function overlap(a, b) {
return ['x', 'y', 'z'].every(k => Math.min(a[k][1], b[k][1]) - Math.max(a[k][0], b[k][0]) > EPS);
}
console.log('\n[2] 간섭 (H형강: 플랜지·웨브 분리 검사)');
const items = d.parts.map((p, i) => ({ i, comps: components(p) }));
const conflicts = {};
for (let a = 0; a < items.length; a++) {
for (let b = a + 1; b < items.length; b++) {
const A = items[a], B = items[b];
const hit = A.comps.some(ca => B.comps.some(cb => overlap(ca, cb)));
if (hit) {
const key = `${d.parts[A.i].memberType} ↔ ${d.parts[B.i].memberType}`;
conflicts[key] = (conflicts[key] || 0) + 1;
}
}
}
const keys = Object.keys(conflicts);
if (keys.length === 0) {
console.log(' OK 간섭 없음');
} else {
failed = true;
keys.forEach(k => console.log(` NG ${k}: ${conflicts[k]}쌍`));
}
console.log(`\n결과: ${failed ? '실패' : '통과'}`);
process.exit(failed ? 1 : 0);