import type { PierModel } from '../src/core/geometry'; import type { Bounds, Entity, Point, ViewBreak } from './types'; import { cutHatch } from './hatches'; /** Break only a shared, constant-section interior: never the cap or foundation interfaces. */ export function modelBreaks(model:PierModel,scale:number): ViewBreak[] { const result:ViewBreak[]=[]; for(const [member,items] of [['column',model.columns],['pile',model.piles]] as const){ if(!items.length)continue; const diameter=Math.max(...items.map(c=>c.diameter))*1000; const bottom=Math.max(...items.map(c=>c.bottom))*1000,top=Math.min(...items.map(c=>c.top))*1000; const keepBottom=Math.max(member==='column'?900:350,diameter*.65); const keepTop=Math.max(member==='column'?1300:500,diameter*.9); const from=bottom+keepBottom,to=top-keepTop; const gap=member==='column'?diameter+6*scale:1.5*scale; if(to-from>Math.max(gap+500,scale*10))result.push({from,to,gap,member}); } return result.sort((a,b)=>a.from-b.from).filter((b,i,all)=>i===0||b.from>=all[i-1].to); } export function shortenedY(y:number,breaks:readonly ViewBreak[]=[]):number { let removed=0; for(const b of breaks){ if(y<=b.from)break; if(y({...b,minY:shortenedY(b.minY,breaks),maxY:shortenedY(b.maxY,breaks)}); /** Ordinary entities keep full source coordinates. Only their presentation is shortened. */ export function shortenEntity(entity:Entity,breaks:readonly ViewBreak[],clip=true):Entity[] { const point=(p:Point):Point=>[p[0],shortenedY(p[1],breaks)]; if(!breaks.length)return[entity]; if(entity.kind==='text')return[{...entity,point:point(entity.point)}]; if(entity.kind==='circle')return[{...entity,center:point(entity.center)}]; if(entity.kind==='dimension')throw new Error('Expand dimension graphics before presentation.'); if(entity.kind==='hatch')return cutHatch(entity.points,breaks).map(points=>({...entity,points:points.map(point)})); const line=(a:Point,b:Point):Entity[]=>{ if(!clip)return[{...entity,kind:'line',a:point(a),b:point(b)}]; const dy=b[1]-a[1],ts=[0,1]; if(Math.abs(dy)>1e-9)for(const cut of breaks)for(const y of [cut.from,cut.to]){const t=(y-a[1])/dy;if(t>0&&t<1)ts.push(t);} ts.sort((x,y)=>x-y); const at=(t:number):Point=>[a[0]+(b[0]-a[0])*t,a[1]+dy*t]; return ts.slice(1).flatMap((t,i)=>{ const middle=at((ts[i]+t)/2)[1]; if(breaks.some(c=>middle>c.from+1e-7&&middleline(entity.points[i],p)).concat(entity.closed?line(entity.points[entity.points.length-1],entity.points[0]):[]); } export function breakSymbols(entities:Entity[],breaks:readonly ViewBreak[],scale:number):Entity[] { return entities.filter(e=>e.kind==='polyline'&&['column','pile'].includes(e.source.member)).flatMap(e=>{ if(e.kind!=='polyline')return[]; const xs=e.points.map(p=>p[0]),ys=e.points.map(p=>p[1]),left=Math.min(...xs),right=Math.max(...xs),width=right-left; return breaks.filter(b=>e.source.member===b.member&&Math.min(...ys)b.to).flatMap(b=>[b.from,b.to].map(y=>({ kind:'polyline' as const,closed:false,source:e.source,layer:'STRUCTURE' as const, points:[[left-.5*scale,shortenedY(y,breaks)],[left+width*.37,shortenedY(y,breaks)],[left+width*.48,shortenedY(y,breaks)+.65*scale], [left+width*.58,shortenedY(y,breaks)-.65*scale],[left+width*.7,shortenedY(y,breaks)],[right+.5*scale,shortenedY(y,breaks)]] as Point[], }))); }); }