Files
cut-abstractions/tests/dev1/dataHandle/common/LayoutEngine/RemainHelper.ts

405 lines
14 KiB
TypeScript
Raw Normal View History

2025-07-22 18:22:31 +08:00
import type { Polyline } from 'cadapi'
import { Polyline2Points } from 'cadapi'
import { Vector3 } from 'three'
import { ArrayExt } from '../base/ArrayExt.js'
import { Arc2d } from '../base/CAD.js'
import { arrayRemoveDuplicateBySort } from '../ArrayExt.js'
import { InitClipperCpp } from '../ClipperCpp.js'
import type { Vector2 } from '../Vector2.js'
import { Container } from '../core/Container.js'
import { NestCache } from '../core/NestCache.js'
import { ParseOddments } from '../core/ParseOddments.js'
import { Part } from '../core/Part.js'
import { Path } from '../core/Path.js'
import { PathGeneratorSingle } from '../core/PathGenerator.js'
import type { PlaceBlock,PlaceBoard,PlaceMaterial,BlockModel,RemainBlock} from '../../confClass.js'
import { BlockPlus } from './BlockPlus.js'
import { BlockSizePlus } from './BlockSizePlus.js'
import { BlockHelper } from './BlockHelper.js'
// import { PlaceStore } from '../../vo/order/PlaceStore.js'
import { PolylineHelper } from './PolylineHelper.js'
import { CurveWrap, Points2Polyline } from './Curves2Parts.js'
/** 自动生成余料板空间 */
export class RemainHelper {
squarePath: Path
canPutPaths: Path[]
offset = 3.5
initSquarePath() {
// let squareWidth = PlaceStore.sysConfig.scrapBlockSquare || 200;
// let widthMin = PlaceStore.sysConfig.srcapBlockWidthMin || 100;
// let widthMax = PlaceStore.sysConfig.scrapBlockWidthMax || 600;
let squareWidth = 200
let widthMin = 100
let widthMax = 600
this.squarePath = NestCache.CreatePath(60, 60, 0)
this.canPutPaths = [
NestCache.CreatePath(squareWidth, squareWidth, 0),
NestCache.CreatePath(widthMin, widthMax, 0),
NestCache.CreatePath(widthMax, widthMin, 0),
]
}
/** 分析所有余料空间 */
async analyzeAllRemainSpace(pm1: PlaceMaterial, pb1: PlaceBoard, preMillingSize: number) {
this.initSquarePath()
console.log('分析所有余料空间');
if (pm1 && pb1) // 当前板 先做
{
this.offset = (pm1.diameter + pm1.cutKnifeGap) / 2 + preMillingSize
pb1.isCreateRemainSpace = true
await this.analyzeScrapSpace(pm1, pb1)
}
let order = PlaceStore.order
for (let pm of order.materialList) {
this.offset = (pm.diameter + pm.cutKnifeGap ) / 2 + preMillingSize
for (let pb of pm.boardList) {
await this.analyzeScrapSpace(pm, pb)
}
}
}
/** 分析所有余料空间 */
async analyzeScrapSpace(pm: PlaceMaterial, board: PlaceBoard) {
if (board.isCreateRemainSpace == false)
return
await InitClipperCpp()
let cutBoardBorder = PlaceStore.sysConfig.cutBoardBorder;
let cutBoardBorderB = PlaceStore.sysConfig.cutBoardBorderB
if (board.isTwoFaceProcessing == false)
cutBoardBorderB = 0
let binPath: Path
if (board.points && board.points.length > 0) // 非矩形板
{
binPath = new Path(board.points)
}
else // 矩形板
{
binPath = new Path([{ x: cutBoardBorder, y: cutBoardBorder },
{ x: board.width - cutBoardBorderB, y: cutBoardBorder },
{ x: board.width - cutBoardBorderB, y: board.length - cutBoardBorderB },
{ x: cutBoardBorder, y: board.length - cutBoardBorderB },
])
}
binPath.Id = undefined
PathGeneratorSingle.RegisterId(binPath)
// 容器
let container = new Container(binPath)
for (let i = 0; i < board.blockList.length; i++) {
let block = board.blockList[i]
let part = this.toPart(block, binPath, this.offset * 2, i)
// 设置位置
part[0].PlacePosition = { x: (part[1].x + block.placeX + block.placeOffX - cutBoardBorder) * 1e4, y: (part[1].y + block.placeY + block.placeOffY - cutBoardBorder) * 1e4 }
container.PlacedParts.push(part[0])
}
// for (let i = 0; i < board.BlockList.length; i++)
// {
// let block = board.BlockList[i];
// let part = this.toPart(block, binPath, 0, board.BlockList.length + i);
// //设置位置
// part[0].PlacePosition = { x: (part[1].x + block.placeX - border) * 1e4, y: (part[1].y + block.placeY - border) * 1e4 };
// container.PlacedParts.push(part[0]);
// }
// let f = new NestFiler();
// f.Write(container.PlacedParts.length);
// for (let part of container.PlacedParts)
// f.Write(part.State.Contour.BigIntPoints);
// container.WriteFile(f);
// console.log(JSON.stringify(f._datas));
// FileZip.WriteFile(`${board.boardId}.container`, JSON.stringify(f._datas));
board.remainBlockList = [] // 清空
try {
let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths)
for (let space of spaces) {
let sb = this.toRemainBlock(space)
sb.placeX = sb.placeX + cutBoardBorder
sb.placeY = sb.placeY + cutBoardBorder
board.remainBlockList.push(sb)
}
}
catch (err) {
// throw new Error(`计算余料空间异常:${pm.FullName} 第${board.boardId}片`);
console.log(`计算余料空间异常:${pm.fullName}${board.boardId}`, err)
throw new Error(`计算余料空间异常:${pm.fullName}${board.boardId}片;请保存优化联系售后工程师分析`)
}
// 分析小板内 造型孔洞 余料空间
for (let block of board.blockList) {
let childBlocks: PlaceBlock[]
// 造型孔
for (let m of block.models) {
if (m.depth < block.thickness - 0.05)
continue
if (m.hasContour == false)
continue
if (m.isCutting == false)
continue
if (!childBlocks) {
childBlocks = board.blockList.filter(t => t.placeX > block.placeX
&& t.placeX + t.placeWidth < block.placeX + block.placeWidth
&& t.placeY > block.placeY
&& t.placeY + t.placeLength < block.placeY + block.placeLength)
}
let spaces = await this.getModelSpace(block, m, childBlocks)
for (let space of spaces) {
let sb = this.toRemainBlock(space)
// sb.placeX += border;
// sb.placeY += border;
sb.placeX += block.placeX
sb.placeY += block.placeY
board.remainBlockList.push(sb)
}
}
}
board.isCreateRemainSpace = false
// console.log(`board id=${board.boardId} find scrap block count = ${board.remainBlockList.length}`);
// console.log(`${board.boardId} scraping ok`)
}
/** 获取造型空间 */
async getModelSpace(block: PlaceBlock, m: BlockModel, blocks: PlaceBlock[]): Promise<any[]> {
if (m.originModeling == null)
return []
let pts = m.originModeling.outline.pts
let buls = m.originModeling.outline.buls
if (pts == undefined) {
pts = m.originModeling.outline.map(e => e.pts)
}
if (buls == undefined) {
buls = m.originModeling.outline.map(e => e.buls)
}
// blocks = [];
// 起点
let pts_n = []
for (let i = 0; i < pts.length; i++) {
let p0 = BlockHelper.getPlaceXYInBlock(block, pts[i].x, pts[i].y, false, false)
pts_n.push({ x: p0.x, y: p0.y, bul: buls[i] })
}
// 如果反面,翻转方向,重设坐标
if (block.isTurnOver)
pts_n = this.reversePoint(pts_n, block.cutWidth)
// pts_n.forEach(t => { t.x += block.placeX; t.y += block.placeY; });
let polyLine
polyLine = Points2Polyline(pts_n)
let cicle = PolylineHelper.ConverPolyLin2Circle(polyLine)
if (cicle)
polyLine = cicle
let cureW2 = new CurveWrap(polyLine, this.offset, false)
let pts2 = cureW2.GetInsidePoints2(this.offset)
if (!pts2)
return []
let posx = 10000
let posy = 10000
for (let p of pts2) {
if (p.x < posx)
posx = p.x
if (p.y < posy)
posy = p.y
}
for (let p of pts2) {
p.x -= posx
p.y -= posy
}
let binPath = new Path(pts2)
if (binPath.Area < 15000)
return []
await InitClipperCpp()
binPath.Id = undefined
PathGeneratorSingle.RegisterId(binPath)
// 容器
let container = new Container(binPath)
let i = 0
for (let b of blocks) {
let part = this.toPart(b, binPath, this.offset, i)
if (part[0].State.Contour == null)
continue
part[0].Id = i++
// let isin = this.blockInBlock(block, b, polyLine);
// if (isin == false) continue;
// let part = this.toPart(b, binPath, this.offset, i++);
// 设置位置
part[0].PlacePosition = { x: (b.placeX - block.placeX - posx) * 1e4, y: (b.placeY - block.placeY - posy) * 1e4 }
container.PlacedParts.push(part[0])
}
try {
// let f = new NestFiler();
// f.Write(container.PlacedParts.length);
// for (let part of container.PlacedParts)
// f.Write(part.State.Contour.BigIntPoints);
// container.WriteFile(f);
// console.log(JSON.stringify(f._datas));
// FileZip.WriteFile(`${board.boardId}.container`, JSON.stringify(f._datas));
let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths)
for (let space of spaces) {
space.Points.forEach((t) => { t.x += posx; t.y += posy })
}
return spaces
}
catch (err) {
console.log(`${block.blockNo}造型孔分析余料空间失败.`)
throw new Error(`${block.blockNo}造型孔分析余料空间失败.请保存优化联系售后工程师分析`)
}
// let spaces = ParseOddments(container, binPath, this.offset, this.squarePath, this.canPutPaths);
// for (let space of spaces)
// {
// space.Points.forEach(t => { t.x += posx; t.y += posy; });
// }
// return spaces;
}
/** 转配件 */
toPart(block: PlaceBlock, binPath: Path, offset: number, id: number): [Part, Vector2] {
let w = 10000
let binPath2 = new Path([{ x: -w, y: -w }, { x: w, y: -w }, { x: w, y: w }, { x: -w, y: w }])
let part = new Part()
part.Id = id
part.UserData = { bno: block.blockNo, area: block.area, isUnRegular: block.isUnRegular, width: block.cutWidth, length: block.cutLength }
let path: Path
let bPoint: Vector2
let sizeOff = BlockSizePlus.getOffDis(block) // 外扩偏移
let hasSizeOff = (sizeOff.left + sizeOff.right + sizeOff.top + sizeOff.bottom) > (block.placeMaterial.diameter * 2 + block.placeMaterial.cutKnifeGap * 2 + 0.01)
// 矩形且带板外不偏移
if (block.isUnRegular == false && hasSizeOff == false) {
path = NestCache.CreatePath(block.placeWidth + sizeOff.left + sizeOff.right, block.placeLength + sizeOff.top + sizeOff.bottom, offset)
bPoint = path.OrigionMinPoint
// 轮廓
part.Init2(path, binPath2, [0])
}
else // 异形
{
let pts = []
let cured = BlockPlus.getBorder_moving(block)
for (let l of cured) {
if (l instanceof Arc2d) {
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul })
}
else {
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 })
}
}
let polyLine
polyLine = Points2Polyline(pts)
polyLine.CloseMark = true
let cureW = new CurveWrap(polyLine, offset, true)
let pts2 = cureW.GetOutsidePoints()
arrayRemoveDuplicateBySort(pts, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
path = new Path(pts2)
let cicle = PolylineHelper.ConverPolyLin2Circle(polyLine)
if (!cicle) {
let area = path.BoundingBox.area - path.Area
if (area < 15000 && pts.length > 6)
path = NestCache.CreatePath(block.placeWidth, block.placeLength, offset)
}
part.Init2(path, binPath2, [0])
// 不能放下,那么尝试不简化路径
if (!path.IsRect && !cicle && part.RotatedStates.length == 0) {
let pts2 = Polyline2Points(polyLine, true, offset)[1]
arrayRemoveDuplicateBySort(pts2, (p1, p2) => (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) < 1e-2)
path = new Path(pts2)
part.Init2(path, binPath, [0])
}
// 轮廓
bPoint = path.OrigionMinPoint
}
if (hasSizeOff) {
bPoint.x = bPoint.x - sizeOff.left
bPoint.y = bPoint.y - sizeOff.bottom
}
return [part, bPoint]
}
/** 余料板空间 */
toRemainBlock(path: Path): RemainBlock {
let bx = path.OrigionMinPoint.x
let by = path.OrigionMinPoint.y
let points = path.Points
let ptsX = points.map((t) => { return t.x })
let ptsY = points.map((t) => { return t.y })
let x0 = ArrayExt.min(ptsX, t => t)
let y0 = ArrayExt.min(ptsY, t => t)
let x1 = ArrayExt.max(ptsX, t => t)
let y1 = ArrayExt.max(ptsY, t => t)
let sp = new RemainBlock(x0, y0, x1 - x0, y1 - y0)
let pts = points.map((t) => { return { x: t.x - x0, y: t.y - y0 } })
sp.placeX = bx + x0
sp.placeY = by + y0
sp.setPoints(pts)
return sp
}
/** 判断板是否在造型洞里头 */
blockInModelOfBlock(block: PlaceBlock, b: PlaceBlock, modlPl: Polyline, posX = 0, posY = 0): boolean {
let cured = BlockPlus.getBorder(b)
let pts = []
for (let l of cured) {
if (l instanceof Arc2d) {
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: l.Bul })
}
else {
pts.push({ x: l.StartPoint.m_X, y: l.StartPoint.m_Y, bul: 0 })
}
}
let polyLine = Points2Polyline(pts)
let cureW2 = new CurveWrap(polyLine, this.offset, false)
let pts2 = cureW2.GetInsidePoints2(this.offset)
// pts2 = pts2.map(t => { return { x: t.x + b.placeX - block.placeX - posX, y: t.y + b.placeY - block.placeY - posY }; });
for (let t of pts2) {
let x = t.x + b.placeX - block.placeX - posX
let y = t.y + b.placeY - block.placeY - posY
if (modlPl.PtInCurve(new Vector3(x, y, 0)) == false)
return false
}
return true
}
/** 反向点 */
private reversePoint(pts, w): any[] {
let newPts = []
for (let i = pts.length - 1; i >= 0; i--) {
let p = pts[i]
let x = p.x
let y = p.y
let j = i == 0 ? pts.length - 1 : i - 1
let bul = pts[j].bul
newPts.push({ x, y, bul })
}
return newPts
}
}