import { ClipType, EndType, JoinType, PolyFillType } from 'js-angusj-clipper/web' import type { ClipInput } from 'js-angusj-clipper/web' import { Box2 } from '..//Box2' import { clipperCpp } from '../ClipperCpp' import type { Container } from './Container' import { NestCache } from './NestCache' import { Path, PathScale, TranslatePath, TranslatePath_Self } from './Path' const SquarePath = NestCache.CreatePath(60, 60, 0) const CanPutPaths = [ NestCache.CreatePath(200, 200, 0), NestCache.CreatePath(600, 100, 0), NestCache.CreatePath(100, 600, 0), ] /** * 分析排料结果的余料 * @param container 排料结果的容器 * @param binPath 容器的bin * @param [knifeRadius] 刀半径(以便我们再次偏移) * @param squarePath 使用一个正方形路径来简化余料轮廓 * @param canPutPaths 使用可以放置的路径列表来测试余料是否可用,如果可用,则保留 * @returns Path[] 轮廓的位置存储在OrigionMinPoint中 */ export function ParseOddments(container: Container, binPath: Path, knifeRadius: number = 3.5, squarePath: Path = SquarePath, canPutPaths: Path[] = CanPutPaths): Path[] { // 构建轮廓数据 let partPaths: ClipInput[] = container.PlacedParts.map((part) => { // 直接在这里偏移,而不缓存,应该没有性能问题 let newPts = clipperCpp.lib.offsetToPaths({ delta: knifeRadius * 1e4, offsetInputs: [{ data: part.State.Contour.BigIntPoints, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }], })[0] let path = TranslatePath(newPts, { x: part.PlacePosition.x - 5e3, y: part.PlacePosition.y - 5e3 })// 因为移动了0.5,0.5,所以这里也要移动0.5 return { data: path } }) // console.log('构建轮廓数据', partPaths) // 所有的余料(使用布尔差集) let oddmentsPolygon = clipperCpp.lib.clipToPolyTree({ subjectInputs: [{ data: binPath.BigIntPoints, closed: true }], clipInputs: partPaths, clipType: ClipType.Difference, subjectFillType: PolyFillType.NonZero, }) // 现在我们用树状结构,应该不会自交了?(文档写了,返回的结果不可能重叠或者自交) // 简化结果,避免自交 // oddmentsPolygon = clipperCpp.lib.simplifyPolygons(oddmentsPolygon); function CreatePolygon(minx: number, miny: number, maxx: number, maxy: number) { return [ { x: minx, y: miny }, { x: maxx, y: miny }, { x: maxx, y: maxy }, { x: minx, y: maxy }, ] } let clipedPaths: Path[] = []// 已经减去网洞投影的余料轮廓列表 // 由于手动排版可能造成余料网洞,我们将网洞的盒子投影,然后裁剪余料,避免余料有网洞 for (let node of oddmentsPolygon.childs) { let nodePolygon = node.contour // 减去网洞 if (node.childs.length) { let box = new Box2().setFromPoints(nodePolygon) let childBoxPolygon = node.childs.map((cnode) => { let cbox = new Box2().setFromPoints(cnode.contour) let type = 0// 0左1右2上3下 let minDist = Number.POSITIVE_INFINITY let letftDist = cbox.min.x - box.min.x let rightDist = box.max.x - cbox.max.x let topDist = box.max.y - cbox.max.y let downDist = cbox.min.y - box.min.y if (rightDist < letftDist) { type = 1 minDist = rightDist } if (topDist < minDist) { type = 2 minDist = topDist } if (downDist < minDist) type = 3 if (type === 0) return CreatePolygon(box.min.x, cbox.min.y, cbox.max.x, cbox.max.y) if (type === 1) return CreatePolygon(cbox.min.x, cbox.min.y, box.max.x, cbox.max.y) if (type === 2) return CreatePolygon(cbox.min.x, cbox.min.y, cbox.max.x, box.max.y) if (type === 3) return CreatePolygon(cbox.min.x, box.min.y, cbox.max.x, cbox.max.y) }) let splits = clipperCpp.lib.clipToPaths({ subjectInputs: [{ data: nodePolygon, closed: true }], clipInputs: childBoxPolygon.map((polygon) => { return { data: polygon } }), clipType: ClipType.Difference, subjectFillType: PolyFillType.NonZero, }) for (let p of splits) clipedPaths.push(new Path(PathScale(p, 1e-4))) } else clipedPaths.push(new Path(node.contour.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } }))) } let OddmentsPaths: Path[] = [] for (let polygonPath of clipedPaths) { // 先获取内部的nfp let insideNFPS = polygonPath.GetInsideNFP(squarePath) if (!insideNFPS) continue let beferPolygons: ClipInput[] = [] for (let nfp of insideNFPS) { let nfpPath = new Path(PathScale(nfp, 1e-4)) // 通过内部nfp还原实际轮廓 let sumPolygons = clipperCpp.lib.minkowskiSumPath(nfpPath.BigIntPoints, squarePath.BigIntPoints, true) sumPolygons = clipperCpp.lib.simplifyPolygons(sumPolygons) for (let poly of sumPolygons) { if (clipperCpp.lib.area(poly) < 0) continue// 移除内部的,无意义的 let tempPath = new Path(poly.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } }))// 这里new一个新的,下面就复用这个 if (canPutPaths.some(p => tempPath.GetInsideNFP(p)?.length))// 能塞的下指定的轮廓才会被留下 { if (beferPolygons.length) { // 移动到实际位置 TranslatePath_Self(poly, (polygonPath.OrigionMinPoint.x + nfpPath.OrigionMinPoint.x) * 1e4, (polygonPath.OrigionMinPoint.y + nfpPath.OrigionMinPoint.y) * 1e4) // 在这里裁剪之前的余料轮廓 let tree = clipperCpp.lib.clipToPolyTree({ subjectInputs: [{ data: poly, closed: true }], clipInputs: beferPolygons, clipType: ClipType.Difference, subjectFillType: PolyFillType.NonZero, }) for (let node of tree.childs) { if (node.childs.length) continue tempPath = new Path(node.contour.map((p) => { return { x: p.x * 1e-4, y: p.y * 1e-4 } })) // 继续简化 tempPath = SimplifyPathOfSqPath(tempPath, squarePath) if (!tempPath) continue OddmentsPaths.push(tempPath) // 偏移2把刀 let offsetedPolygon = clipperCpp.lib.offsetToPaths({ delta: knifeRadius * 2e4, offsetInputs: [{ data: node.contour, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }], })[0] beferPolygons.push({ data: offsetedPolygon })// 用于裁剪后续的余料 } } else { // 设置轮廓的位置 tempPath.OrigionMinPoint.x += nfpPath.OrigionMinPoint.x + polygonPath.OrigionMinPoint.x tempPath.OrigionMinPoint.y += nfpPath.OrigionMinPoint.y + polygonPath.OrigionMinPoint.y OddmentsPaths.push(tempPath) // 将余料轮廓加入到裁剪轮廓中,用于裁剪后续的余料 if (insideNFPS.length) { // 移动到实际位置 TranslatePath_Self(poly, (polygonPath.OrigionMinPoint.x + nfpPath.OrigionMinPoint.x) * 1e4, (polygonPath.OrigionMinPoint.y + nfpPath.OrigionMinPoint.y) * 1e4) // 偏移2把刀 let offsetedPolygon = clipperCpp.lib.offsetToPaths({ delta: knifeRadius * 2e4, offsetInputs: [{ data: poly, joinType: JoinType.Miter, endType: EndType.ClosedPolygon }], })[0] beferPolygons.push({ data: offsetedPolygon })// 用于裁剪后续的余料 } } } } } } // console.log('ParseOddments end', OddmentsPaths) return OddmentsPaths } // 使用矩形轮廓来简化余料轮廓(通常一进一出) function SimplifyPathOfSqPath(polygonPath: Path, sqPath: Path): Path | undefined { // 先获取内部的nfp let insideNFPS = polygonPath.GetInsideNFP(sqPath) if (insideNFPS.length > 0)// 目前一般只有1个,不知道会不会有多个 { let nfp = insideNFPS[0] let nfpPath = new Path(PathScale(nfp, 1e-4)) // 通过内部nfp还原实际轮廓 let sumPolygons = clipperCpp.lib.minkowskiSumPath(nfpPath.BigIntPoints, sqPath.BigIntPoints, true) sumPolygons = clipperCpp.lib.simplifyPolygons(sumPolygons) for (let poly of sumPolygons)// 通常是一个内部的+一个外部的 { if (clipperCpp.lib.area(poly) < 0) continue// 移除内部的,无意义的 let tempPath = new Path(PathScale(poly, 1e-4)) tempPath.OrigionMinPoint.x += nfpPath.OrigionMinPoint.x + polygonPath.OrigionMinPoint.x tempPath.OrigionMinPoint.y += nfpPath.OrigionMinPoint.y + polygonPath.OrigionMinPoint.y return tempPath } } }