const fs = require('fs'); const DxfParser = require('c:/Users/user/.gemini/tunnelsection/TunnelModeler3D/dxf-parser.js'); function analyzeWallGeometry(dxfPath, label) { if (!fs.existsSync(dxfPath)) return; console.log(`\n======================================================`); console.log(`[${label}] 공동구 선분 기하 정밀 실측: ${dxfPath}`); const dxf = new DxfParser().parseSync(fs.readFileSync(dxfPath, 'utf8')); // app.js와 동일한 방식으로 targetCenterX, targetCenterY, pavement 선분 찾기 let appCode = fs.readFileSync('c:/Users/user/.gemini/tunnelsection/TunnelModeler3D/app.js', 'utf8'); // mock 환경 global.window = { current3DTunnelLength: 10, current3DOpacity: 1.0, current3DOuterOpacity: 1.0, current3DViewMode: 'bird', current3DTheme: 'dark', current3DVisibility: {}, sectionParams: {}, ugData: { left: {}, right: {} }, addEventListener: () => {}, lastGeneratedLoops2D: [], view2D: { zoom: 1, panX: 0, panY: 0 } }; const mockCtx = { fillRect: ()=>{}, strokeRect: ()=>{}, beginPath: ()=>{}, closePath: ()=>{}, moveTo: ()=>{}, lineTo: ()=>{}, stroke: ()=>{}, fill: ()=>{}, save: ()=>{}, restore: ()=>{}, setTransform: ()=>{}, fillText: ()=>{} }; global.document = { getElementById: () => ({ classList: { add: ()=>{}, remove: ()=>{} }, addEventListener: () => {}, value: '', disabled: false, getContext: () => mockCtx, getBoundingClientRect: () => ({ width: 800, height: 600 }) }), querySelectorAll: () => [] }; global.$ = (id) => global.document.getElementById(id); global.Plotly = { react: () => {} }; eval(appCode); extractParamsFromDXF(dxf, label); const sp = window.sectionParams; const pL_X = sp.pavementLeftX; const pR_X = sp.pavementRightX; // 단면 선분들 중 좌측 공동구 영역(X: pL_X - 1500 ~ pL_X)과 우측 공동구 영역(X: pR_X ~ pR_X + 1500) const allLines = dxf.entities.filter(e => e.type === 'LINE'); // 좌측 공동구 상단선(수평선) 탐색 const leftHLines = allLines.filter(l => { const minX = Math.min(l.vertices[0].x, l.vertices[1].x); const maxX = Math.max(l.vertices[0].x, l.vertices[1].x); const dy = Math.abs(l.vertices[0].y - l.vertices[1].y); const dx = Math.abs(l.vertices[0].x - l.vertices[1].x); return minX >= pL_X - 2500 && maxX <= pL_X + 100 && dy <= 3.0 && dx >= 40; }); // 우측 공동구 상단선(수평선) 탐색 const rightHLines = allLines.filter(l => { const minX = Math.min(l.vertices[0].x, l.vertices[1].x); const maxX = Math.max(l.vertices[0].x, l.vertices[1].x); const dy = Math.abs(l.vertices[0].y - l.vertices[1].y); const dx = Math.abs(l.vertices[0].x - l.vertices[1].x); return minX >= pR_X - 100 && maxX <= pR_X + 2500 && dy <= 3.0 && dx >= 40; }); // 도로 표면 기준 높이(포장 끝단 Y) // roadOriginY는 대략 포장 끝점 Y const pL_Y = sp.roadOriginY || 0; // sp에는 없음, app.js 내 pL_Y 찾기 console.log(` 포장 좌측단 X=${Math.round(pL_X)}, 우측단 X=${Math.round(pR_X)}`); console.log(` [좌측 수평선들 (X, Y, 길이 dx)]:`); leftHLines.sort((a,b) => b.vertices[0].y - a.vertices[0].y).slice(0, 10).forEach(l => { const x1 = Math.round(l.vertices[0].x), x2 = Math.round(l.vertices[1].x); const y = Math.round((l.vertices[0].y + l.vertices[1].y)/2); const len = Math.round(Math.abs(x1 - x2)); console.log(` Y=${y}, X=[${Math.min(x1,x2)} ~ ${Math.max(x1,x2)}], dx=${len}, layer=${l.layer}`); }); console.log(` [우측 수평선들 (X, Y, 길이 dx)]:`); rightHLines.sort((a,b) => b.vertices[0].y - a.vertices[0].y).slice(0, 10).forEach(l => { const x1 = Math.round(l.vertices[0].x), x2 = Math.round(l.vertices[1].x); const y = Math.round((l.vertices[0].y + l.vertices[1].y)/2); const len = Math.round(Math.abs(x1 - x2)); console.log(` Y=${y}, X=[${Math.min(x1,x2)} ~ ${Math.max(x1,x2)}], dx=${len}, layer=${l.layer}`); }); } analyzeWallGeometry('c:/Users/user/.gemini/tunnelsection/TunnelModeler3D/Test Sample/세종안성_서운1.dxf', '서운1'); analyzeWallGeometry('c:/Users/user/.gemini/tunnelsection/TunnelModeler3D/Test Sample/안성구리3_고삼.dxf', '고삼'); analyzeWallGeometry('c:/Users/user/.gemini/antigravity/brain/9c348cd5-3f07-4ade-adba-e28b2709935f/scratch/wansan_out/wansan.dxf', '완산'); analyzeWallGeometry('c:/Users/user/.gemini/antigravity/brain/9c348cd5-3f07-4ade-adba-e28b2709935f/scratch/namhan_out/namhan.dxf', '남한산성');