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