import type { Point, Shape } from './types'; export type Bounds = { minX: number; minY: number; maxX: number; maxY: number }; export function bounds(points: Point[]): Bounds { let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; for (const p of points) { if (p.x < minX) minX = p.x; if (p.y < minY) minY = p.y; if (p.x > maxX) maxX = p.x; if (p.y > maxY) maxY = p.y; } return { minX, minY, maxX, maxY }; } export function shapeSize(shape: Shape): { width: number; height: number } { const b = bounds(shape.outer); return { width: b.maxX - b.minX, height: b.maxY - b.minY }; } export function simplifyRing(ring: Point[], tolerance: number): Point[] { if (ring.length <= 3) return ring.slice(); // Split at the vertex farthest from the start so each half is an open polyline. let far = 0; let farDist = -1; for (let i = 1; i < ring.length; i++) { const d = Math.hypot(ring[i].x - ring[0].x, ring[i].y - ring[0].y); if (d > farDist) { farDist = d; far = i; } } const a = douglasPeucker(ring.slice(0, far + 1), tolerance); const b = douglasPeucker([...ring.slice(far), ring[0]], tolerance); return [...a.slice(0, -1), ...b.slice(0, -1)]; } function douglasPeucker(points: Point[], tolerance: number): Point[] { if (points.length < 3) return points; const first = points[0]; const last = points[points.length - 1]; let index = 0; let maxDist = 0; for (let i = 1; i < points.length - 1; i++) { const d = distanceToSegment(points[i], first, last); if (d > maxDist) { maxDist = d; index = i; } } if (maxDist <= tolerance) return [first, last]; const left = douglasPeucker(points.slice(0, index + 1), tolerance); const right = douglasPeucker(points.slice(index), tolerance); return [...left.slice(0, -1), ...right]; } function distanceToSegment(p: Point, a: Point, b: Point): number { const dx = b.x - a.x; const dy = b.y - a.y; const len2 = dx * dx + dy * dy; if (len2 === 0) return Math.hypot(p.x - a.x, p.y - a.y); const t = Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2)); return Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy)); } export function prepareForExport(shapePx: Shape, pxPerMm: number, toleranceMm = 0.5): Shape | null { const toMm = (ring: Point[]) => simplifyRing(ring.map((p) => ({ x: p.x / pxPerMm, y: p.y / pxPerMm })), toleranceMm); const outer = toMm(shapePx.outer); if (outer.length < 3) return null; const holes = shapePx.holes.map(toMm).filter((hole) => hole.length >= 3); const { minX, minY } = bounds(outer); const shift = (ring: Point[]) => ring.map((p) => ({ x: p.x - minX, y: p.y - minY })); return { outer: shift(outer), holes: holes.map(shift) }; } export function scrapFilename(shapeMm: Shape, ext: 'dxf' | 'svg'): string { const { width, height } = shapeSize(shapeMm); return `scrap-${Math.round(width)}x${Math.round(height)}mm.${ext}`; } export function formatMm(n: number): string { const rounded = Number(n.toFixed(3)); return String(rounded === 0 ? 0 : rounded); }