- 받침블럭 배근 경로를 교대 3D 철근에 추가 (선택 · 색 기준 공통), 방위각 회전 반영 - 콘크리트 안 검사에 받침블럭 포함 (다리 묻힘 포함 모두 안) - 테스트: 블럭 5개 × 8본씩 3조각, 콘크리트 안 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
84 lines
5.4 KiB
TypeScript
84 lines
5.4 KiB
TypeScript
// 교대 A1 3D 철근 중 콘크리트 밖으로 나간 부분을 찾는다: 철근 축을 ~100 mm 간격으로 찍어 콘크리트 메시(저판 · 벽체 · 흉벽 · 날개벽 ·
|
|
// 헌치 · 받침블럭) 중 하나라도 안에 드는지 광선 교차 수의 홀짝으로 본다. 결과 JSON = 철근 번호별 밖 점 수와 예.
|
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import * as THREE from 'three';
|
|
import * as XLSX from 'xlsx';
|
|
import { 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 { resolveSlab, slabGradePct, slabSection } from '../../src/core/abut/slab';
|
|
import { abutSlabGeom, abutSlabRebar } from '../../src/core/abut/rebar/slab';
|
|
import { abutBlockRebar } from '../../src/core/abut/rebar/block';
|
|
import { abutFootingGeom, abutFootingRebar } from '../../src/core/abut/rebar/footing';
|
|
import { skewAngle } from '../../src/core/abut/rebar/skew-layout';
|
|
import { abutWallGeom, abutWallRebar } from '../../src/core/abut/rebar/wall';
|
|
import { abutParapetGeom, abutParapetRebar, slabSpanAt } from '../../src/core/abut/rebar/parapet';
|
|
import { abutWingGeom, abutWingRebar } from '../../src/core/abut/rebar/wing';
|
|
import { abutPlanOutline, abutSkewK, buildAbutGroup } from '../../src/view/abut-scene';
|
|
import { buildAbutRebar3D } from '../../src/view/abut-rebar3d';
|
|
|
|
it('dump 콘크리트 밖 철근', () => {
|
|
const out = process.env.DUMP_OUT ?? fileURLToPath(new URL('./out', import.meta.url));
|
|
mkdirSync(out, { recursive: true });
|
|
const xlsx = fileURLToPath(new URL('../../../../RAG/운곡교P1입력.xlsx', import.meta.url));
|
|
const rec = parseAbutments(XLSX.read(readFileSync(xlsx), { type: 'buffer' })).abutments[0];
|
|
const model = buildAbut(toAbutInput(rec));
|
|
const members = buildMembers(model, { bottomEl: rec.foundationBottomEl, pileLengthMm: rec.pileLengthMm });
|
|
const wings = buildWings(model, members);
|
|
const angle = skewAngle(rec.skewDeg);
|
|
const fg = abutFootingGeom(model.length, members.shape, angle);
|
|
const footing = abutFootingRebar(fg);
|
|
const wg = abutWallGeom(model.length, members, angle);
|
|
const wall = abutWallRebar(wg, footing);
|
|
const slab = resolveSlab();
|
|
const k = abutSkewK(model);
|
|
const tM = (wg.xb / 1000 + members.shape.parapetBackM) / 2;
|
|
const span = slabSpanAt(abutPlanOutline(model, members, wings, slab).slab, tM, k)!;
|
|
const pg = abutParapetGeom(wg, members.shape, model.bearings[0].s, { B: model.wing!.slabWidthMm, a: span.a, b: span.b, nearT: members.shape.parapetBackM * 1000 - slab.parapetGapMm, chamferA: 300, chamferB: 300 });
|
|
const para = abutParapetRebar(pg, wall);
|
|
const seats = members.wallSegments.map((sg) => ({ from: sg.sFrom * 1000, to: sg.sTo * 1000, z: (sg.seatEl - members.footingBottomEl!) * 1000 }));
|
|
const wingR = wings.map((w) => abutWingRebar(abutWingGeom(model, members, w, footing.z.B68, seats, k)));
|
|
const grade = slabGradePct(slab, model.wing?.slopePct ?? 0);
|
|
const slabG = abutSlabGeom(slab.form, abutPlanOutline(model, members, wings, slab).slab, slabSection(slab, grade, members.shape), { startEl: members.parapetTopStartEl, endEl: members.parapetTopEndEl, L: model.length, bottomEl: members.footingBottomEl! }, [300, 300], slab.lengthMm, grade);
|
|
const blockIn = model.bearings.map((b) => ({ no: b.no, s: b.s, t: model.frontSide * b.lateral, rotDeg: b.rotDeg, seatEl: b.seatEl, blockHeightMm: b.blockHeightMm }));
|
|
const blockR = abutBlockRebar(blockIn, members.footingBottomEl!);
|
|
const rebar = buildAbutRebar3D(fg, footing, k, members.shape.footingBackM, members.footingBottomEl!, wall, para, wingR, abutSlabRebar(slabG), blockR);
|
|
const group = buildAbutGroup(model, members, wings);
|
|
group.updateMatrixWorld(true);
|
|
const solids: THREE.Mesh[] = [];
|
|
group.traverse((o) => {
|
|
if (o instanceof THREE.Mesh && !['ground', 'blinding', 'piles'].some((n) => o.parent?.name === n || o.name === n)) solids.push(o);
|
|
});
|
|
const ray = new THREE.Raycaster();
|
|
const inside = (p: THREE.Vector3) =>
|
|
solids.some((m) => {
|
|
ray.set(p, new THREE.Vector3(0.577, 0.577, 0.577));
|
|
return ray.intersectObject(m, false).length % 2 === 1;
|
|
});
|
|
const res: Record<string, { out: number; total: number; ex: number[][] }> = {};
|
|
for (const [name, meshes] of rebar.byName) {
|
|
for (const mesh of meshes) {
|
|
const geo = mesh.geometry as THREE.CylinderGeometry;
|
|
geo.computeBoundingBox();
|
|
// Cylinder axis: the two cap centres = bbox centre ± half length along its direction; sample the bbox diagonal's centre line instead.
|
|
const pos = geo.getAttribute('position');
|
|
const a = new THREE.Vector3(pos.getX(0), pos.getY(0), pos.getZ(0));
|
|
const b = new THREE.Vector3(pos.getX(pos.count - 1), pos.getY(pos.count - 1), pos.getZ(pos.count - 1));
|
|
const n = Math.max(2, Math.ceil(a.distanceTo(b) / 0.1));
|
|
const r = (res[name] ??= { out: 0, total: 0, ex: [] });
|
|
for (let i = 0; i <= n; i++) {
|
|
const p = a.clone().lerp(b, i / n);
|
|
r.total++;
|
|
if (!inside(p)) {
|
|
r.out++;
|
|
if (r.ex.length < 3) r.ex.push([p.x, p.y, p.z].map((v) => Math.round(v * 1000) / 1000));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
writeFileSync(`${out}/outside.json`, JSON.stringify(Object.fromEntries(Object.entries(res).filter(([, v]) => v.out > 0)), null, 1));
|
|
});
|