Files
Pier_v1/도면작성/occupancy.ts
T
2026-11-04 13:34:51 +09:00

38 lines
3.1 KiB
TypeScript

import { presentationEntities } from './presentation';
import type { Bounds,DrawingView,Entity,Point,QualityIssue } from './types';
export const overlaps=(a:Bounds,b:Bounds,pad=0)=>Math.min(a.maxX,b.maxX)-Math.max(a.minX,b.minX)>pad&&Math.min(a.maxY,b.maxY)-Math.max(a.minY,b.minY)>pad;
export const union=(boxes:Bounds[]):Bounds=>boxes.length?{minX:Math.min(...boxes.map(b=>b.minX)),minY:Math.min(...boxes.map(b=>b.minY)),maxX:Math.max(...boxes.map(b=>b.maxX)),maxY:Math.max(...boxes.map(b=>b.maxY))}:{minX:0,minY:0,maxX:1,maxY:1};
export function textWidth(text:string,height:number):number{return Math.max(height*.35,[...text].reduce((s,c)=>s+(c.charCodeAt(0)>255?1:/[il., :;'|]/.test(c)?.3:/[MW@]/.test(c)?.88:.6),0)*height);}
export function entityBox(e:Entity):Bounds {
let ps:Point[]=[];
if(e.kind==='text'){
const w=textWidth(e.text,e.height),left=e.align==='center'?-w/2:e.align==='right'?-w:0,theta=(e.rotation??0)*Math.PI/180;
ps=[[left,-.22*e.height],[left+w,-.22*e.height],[left+w,e.height],[left,e.height]].map(([x,y])=>[e.point[0]+x*Math.cos(theta)-y*Math.sin(theta),e.point[1]+x*Math.sin(theta)+y*Math.cos(theta)]);
}else if(e.kind==='circle')ps=[[e.center[0]-e.radius,e.center[1]-e.radius],[e.center[0]+e.radius,e.center[1]+e.radius]];
else if(e.kind==='line')ps=[e.a,e.b];else if(e.kind==='polyline'||e.kind==='hatch')ps=e.points;
if(!ps.length)return{minX:0,minY:0,maxX:0,maxY:0};
return{minX:Math.min(...ps.map(p=>p[0])),minY:Math.min(...ps.map(p=>p[1])),maxX:Math.max(...ps.map(p=>p[0])),maxY:Math.max(...ps.map(p=>p[1]))};
}
export const occupiedBounds=(view:DrawingView):Bounds=>union(presentationEntities(view).map(p=>entityBox(p.entity)));
const lineHits=(a:Point,b:Point,r:Bounds):boolean=>{
let t0=0,t1=1;const dx=b[0]-a[0],dy=b[1]-a[1];
for(const [p,q] of [[-dx,a[0]-r.minX],[dx,r.maxX-a[0]],[-dy,a[1]-r.minY],[dy,r.maxY-a[1]]]){
if(Math.abs(p)<1e-12){if(q<0)return false;}else{const t=q/p;if(p<0)t0=Math.max(t0,t);else t1=Math.min(t1,t);if(t0>t1)return false;}
}return true;
};
export function viewCollisions(view:DrawingView):QualityIssue[]{
const rendered=presentationEntities(view),texts=rendered.filter(p=>p.entity.kind==='text'),issues:QualityIssue[]=[],s=view.scale;
const structural=rendered.filter(p=>['STRUCTURE','BEARING'].includes(p.entity.layer));
for(let i=0;i<texts.length;i++){
const a=texts[i],box=entityBox(a.entity);
for(const b of texts.slice(i+1))if(overlaps(box,entityBox(b.entity),.1*s))issues.push({view:view.id,kind:'text-text',first:a.index,second:b.index,message:'문자 점유 영역 중첩'});
if(['bearing-detail','bearing-number','keyplan-label','keyplan','column-section-inline'].includes(a.entity.source.member))continue;
for(const p of structural){
const e=p.entity;if(!overlaps(box,entityBox(e),0))continue;
const segments=e.kind==='line'?[[e.a,e.b]]:e.kind==='polyline'?e.points.slice(1).map((b,i)=>[e.points[i],b]).concat(e.closed?[[e.points[e.points.length-1],e.points[0]]]:[]):[];
if(segments.some(([a,b])=>lineHits(a,b,box))){issues.push({view:view.id,kind:'text-structure',first:a.index,second:p.index,message:'문자와 구조 외형선 간섭'});break;}
}
}
return issues;
}