diff --git a/src/plotcutter/plotter.ts b/src/plotcutter/plotter.ts index b3775e2..809f56d 100644 --- a/src/plotcutter/plotter.ts +++ b/src/plotcutter/plotter.ts @@ -19,45 +19,48 @@ export function pts2plotter( ) { const start = startPoint ?? [0, height]; const end = endPoint ?? [0, height]; - + let str = init(width * px2mm, height * px2mm); - // 按 X 轴然后 Y 轴排序路径 - const sorted = pts.slice(); - sorted.sort(function (a, b) { - const [ax, ay] = topleft(a); - const [bx, by] = topleft(b); - - if (ax !== bx) return ax - bx; - return ay - by; - }); + // 使用最近邻算法排序路径 + const sorted = sortPathsByNearestNeighbor(pts, start); // 从起点到第一个路径 if (sorted.length > 0) { const firstPath = sorted[0]; str += ` U${plu(start[0] * px2mm)},${plu((height - start[1]) * px2mm)}`; str += ` D${plu(firstPath[0][0] * px2mm)},${plu((height - firstPath[0][1]) * px2mm)}`; - + // 切割第一个路径 for (let i = 1; i < firstPath.length; i++) { const pt = firstPath[i]; str += ` D${plu(pt[0] * px2mm)},${plu((height - pt[1]) * px2mm)}`; } - + + // 如果第一个路径未闭合,添加闭合命令 + if (!isPathClosed(firstPath)) { + str += ` D${plu(firstPath[0][0] * px2mm)},${plu((height - firstPath[0][1]) * px2mm)}`; + } + // 路径之间移动 for (let i = 1; i < sorted.length; i++) { const prevPath = sorted[i - 1]; const currPath = sorted[i]; - + // 抬刀移动到下一个路径起点 str += ` U${plu(currPath[0][0] * px2mm)},${plu((height - currPath[0][1]) * px2mm)}`; // 下刀切割 str += ` D${plu(currPath[0][0] * px2mm)},${plu((height - currPath[0][1]) * px2mm)}`; - + for (let j = 1; j < currPath.length; j++) { const pt = currPath[j]; str += ` D${plu(pt[0] * px2mm)},${plu((height - pt[1]) * px2mm)}`; } + + // 如果当前路径未闭合,添加闭合命令 + if (!isPathClosed(currPath)) { + str += ` D${plu(currPath[0][0] * px2mm)},${plu((height - currPath[0][1]) * px2mm)}`; + } } } @@ -88,6 +91,72 @@ function topleft(pts: [number, number][]) { return [minx, miny] as [number, number]; } +/** + * 检测路径是否已闭合(起点和终点距离小于阈值) + * @param path 路径点数组 + * @param threshold 距离阈值(像素),默认 0.01 + */ +function isPathClosed(path: [number, number][], threshold = 0.01): boolean { + if (path.length < 2) return true; + const [sx, sy] = path[0]; + const [ex, ey] = path[path.length - 1]; + const dist = Math.sqrt((sx - ex) ** 2 + (sy - ey) ** 2); + return dist < threshold; +} + +/** + * 计算点到路径的最近距离 + * @param pt 点坐标 + * @param path 路径点数组 + */ +function distanceToPath(pt: [number, number], path: [number, number][]): number { + let minDist = Infinity; + for (const p of path) { + const dist = Math.sqrt((pt[0] - p[0]) ** 2 + (pt[1] - p[1]) ** 2); + if (dist < minDist) minDist = dist; + } + return minDist; +} + +/** + * 使用最近邻算法排序路径 + * @param paths 路径数组 + * @param startPos 起始位置 + */ +function sortPathsByNearestNeighbor( + paths: [number, number][][], + startPos: [number, number] +): [number, number][][] { + if (paths.length === 0) return []; + + const result: [number, number][][] = []; + const remaining = paths.slice(); + let currentPos = startPos; + + while (remaining.length > 0) { + // 找到距离当前位置最近的路径 + let nearestIndex = 0; + let nearestDist = Infinity; + + for (let i = 0; i < remaining.length; i++) { + const dist = distanceToPath(currentPos, remaining[i]); + if (dist < nearestDist) { + nearestDist = dist; + nearestIndex = i; + } + } + + // 将最近的路径添加到结果中 + const nearestPath = remaining.splice(nearestIndex, 1)[0]; + result.push(nearestPath); + + // 更新当前位置为该路径的终点 + currentPos = nearestPath[nearestPath.length - 1]; + } + + return result; +} + function init(w: number, h: number) { return ` IN TB26,${plu(w)},${plu(h)} CT1`; }