// Build slab and hollow wall geometry from parsed DXF data. function createCatchpitModel({entities,dimensionTexts,outlineLayer,heightMm,baseThicknessMm,autoHeight}){ const found=CatchpitDxf.closedLoops(entities,outlineLayer); if(!found.length)throw Error('외곽선을 찾지 못했습니다. LN_out 레이어를 확인하세요.'); const dimLines=entities.filter(e=>e.layer==='DIM'&&e.type==='LINE'&&e.pts?.length===2); const candidates=found.map(poly=>({poly,dims:CatchpitDxf.loopDims(poly,dimLines,dimensionTexts),area:Math.abs(CatchpitDxf.area(poly))})).filter(c=>c.dims).sort((a,b)=>a.dims.score-b.dims.score||b.area-a.area); if(!candidates.length)throw Error('평면 외곽에 대응하는 가로·세로 DIM 치수를 찾지 못했습니다.'); const chosen=candidates[0],outer=chosen.poly,minX=Math.min(...outer.map(p=>p.x)),minY=Math.min(...outer.map(p=>p.y)); const insideLoops=found.filter(h=>h!==outer&&CatchpitDxf.inside(h[0],outer)).sort((a,b)=>Math.abs(CatchpitDxf.area(b))-Math.abs(CatchpitDxf.area(a))); if(!insideLoops.length)throw Error('벽체와 내부 공간을 분리하려면 평면도에 안쪽 경계선이 필요합니다.'); const inner=insideLoops[0],scaleX=chosen.dims.scaleX*.001,scaleY=chosen.dims.scaleY*.001; let height=Number(heightMm)*.001,baseThickness=Number(baseThicknessMm)*.001,heightSource='manual'; if(autoHeight){const used=chosen.dims.dyText;const vertical=dimensionTexts.filter(t=>Math.abs((((t.rotation%180)+180)%180)-90)<12&&t!==used).map(t=>({t,value:CatchpitDxf.dimValue(t.text)})).filter(v=>v.value&&v.value>=100).sort((a,b)=>b.value-a.value);if(vertical.length){height=vertical[0].value*.001;heightSource=`section DIM ${vertical[0].value} mm`;const slab=vertical.filter(v=>v.valuea.value-b.value)[0];if(slab)baseThickness=slab.value*.001}} if(!(height>0&&baseThickness>=0))throw Error('벽체 높이와 바닥 두께를 확인해 주세요.'); const outerW=(Math.max(...outer.map(p=>p.x))-minX)*scaleX,outerH=(Math.max(...outer.map(p=>p.y))-minY)*scaleY,innerW=(Math.max(...inner.map(p=>p.x))-Math.min(...inner.map(p=>p.x)))*scaleX,innerH=(Math.max(...inner.map(p=>p.y))-Math.min(...inner.map(p=>p.y)))*scaleY; const wallThickness=((outerW-innerW)+(outerH-innerH))/4; const toModel=p=>({x:(p.x-minX)*scaleX,y:(p.y-minY)*scaleY}); const parts=[{name:'바닥판',type:'base',footprint:outer.map(toModel),z0:-baseThickness,z1:0,color:'#3c9881',visible:true}]; const edgePairs=[],unused=new Set(inner.map((_,i)=>i)); for(let i=0;i