Files
cut-abstractions/tests/dev1/dataHandle/common/core/ParseOddments.ts
2025-07-22 18:22:31 +08:00

239 lines
8.7 KiB
TypeScript

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
}
}
}