239 lines
8.7 KiB
TypeScript
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
|
||
|
}
|
||
|
}
|
||
|
}
|