405 lines
14 KiB
TypeScript
405 lines
14 KiB
TypeScript
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
|
|
}
|
|
}
|