import type { Box2 } from '../Box2' import type { NestFiler } from '../Filer' import type { Point } from '../Vector2' import { RandomIndex } from '../Random' import { FixIndex } from '../Util' import { Vector2 } from '../Vector2' import { GNestConfig } from './GNestConfig' import { NestCache } from './NestCache' import { PartState } from './PartState' import { Path } from './Path' import { PathGeneratorSingle } from './PathGenerator' const EmptyArray = [] /** * 零件类 * 零件类可以绑定数据,也存在位置和旋转状态的信息 * * 初始化零件: * 传入零件的轮廓,刀半径,包围容器(或者为空?) * 初始化用于放置的轮廓。将轮廓首点移动到0点,记录移动的点P。 * * 零件放置位置: * 表示零件轮廓首点的位置。 * * 零件的旋转: * 表示零件轮廓按照首点(0)旋转。 * * 还原零件的放置状态: * 同样将零件移动到0点 * 同样将零件旋转 * 同样将零件移动到指定的位置 * 零件可能处于容器中,变换到容器坐标系 * */ export class Part { Id: number// 用于确定Part的唯一性,并且有助于在其他Work中还原 private _Holes: PartState[] = [] _RotateHoles: PartState[][] = [] // 零件的不同旋转状态,这个数组会在所有的状态间共享,以便快速切换状态 StateIndex = 0 // 旋转状态 RotatedStates: PartState[] = []// 可能的旋转状态列表 PlacePosition: Point // 放置位置(相对于容器的位置) HolePosition: Point// // #region 临时数据(不会被序列化到优化线程) UserData: T// 只用于还原零件的显示状态(或者关联到实际数据) Parent: Part// 如果这个零件放置在了网洞中,这个Parent表示这个网洞所属的零件,我们可以得到这个零件的放置信息,并且可以从Container中的ParentM得到网洞相对于零件的位置 PlaceCS: Matrix// 放置矩阵 Matrix4 PlaceIndex: number// 放置的大板索引 // #endregion GroupMap: { [key: number]: Part[] } = {} get State(): PartState // 零件当前的状态 { return this.RotatedStates[this.StateIndex] } get Holes(): PartState[] { if (GNestConfig.RotateHole) return this._RotateHoles[this.StateIndex] || EmptyArray return this._Holes } // 初始化零件的各个状态,按360度旋转个数 Init(path: Path, bin: Path, rotateCount = 4): this { let rotations: number[] = [] let a = 2 * Math.PI / rotateCount for (let i = 0; i < rotateCount; i++) { rotations.push(a * i) } this.Init2(path, bin, rotations) return this } // 初始化零件的各个状态,按旋转角度表 Init2(path: Path, bin: Path, rotations: number[] = []): this { let pathP = path.OrigionMinPoint let path_0 = PathGeneratorSingle.Allocate(path) let pathSet = new Set() // 初始化零件的状态集合 for (let pa of rotations) { let partState = new PartState() partState.Rotation = pa if (pa === 0) { partState.Contour = path_0 partState.OrigionMinPoint = pathP partState.MinPoint = path.OrigionMinPoint } else { let path_r = new Path(path.Points, pa) partState.Contour = PathGeneratorSingle.Allocate(path_r) partState.Contour.Area = path_0.Area // 避免重复的Path进入State if (pathSet.has(partState.Contour)) continue let p0 = path_r.OrigionMinPoint let c = Math.cos(-pa) let s = Math.sin(-pa) let x1 = p0.x * c - p0.y * s let y1 = p0.x * s + p0.y * c partState.OrigionMinPoint = new Vector2(pathP.x + x1, pathP.y + y1) // 计算正确的最小点 let tempPath = new Path(path.OrigionPoints, pa) partState.MinPoint = tempPath.OrigionMinPoint } // 记录已有Path pathSet.add(partState.Contour) // 必须能放置 if (bin.GetInsideNFP(partState.Contour)) { this.RotatedStates.push(partState) PathGeneratorSingle.RegisterId(partState.Contour) } } // 为了复用NFP,不管第0个Path是否可用,都注册它. if (this.RotatedStates.length > 4) PathGeneratorSingle.RegisterId(path_0) return this } ParseGroup(partOther: Part, bin: Path): Part[] { let arr = this.GroupMap[partOther.Id] if (arr) return arr arr = [] if (this.Holes.length || partOther.Holes.length) return arr this.GroupMap[partOther.Id] = arr if (this.State.Contour.IsRect || partOther.State.Contour.IsRect) return arr if (this.State.Contour.Area > this.State.Contour.BoundingBox.area * 0.9) return arr if (partOther.State.Contour.Area > partOther.State.Contour.BoundingBox.area * 0.9) return arr for (let i = 0; i < this.RotatedStates.length; i++) { let s1 = this.RotatedStates[i] for (let j = 1; j < partOther.RotatedStates.length; j++) { let s2 = partOther.RotatedStates[j] let nfps = s1.Contour.GetOutsideNFP(s2.Contour) for (let nfp of nfps) { for (let k = 0; k < nfp.length * 2; k++) { let p: Point if (k % 2 === 0) { p = { ...nfp[k / 2] } } else { let p1 = nfp[FixIndex(k / 2 - 0.5, nfp)] let p2 = nfp[FixIndex(k / 2 + 0.5, nfp)] p = { x: p1.x + p2.x, y: p1.y + p2.y } p.x *= 0.5 p.y *= 0.5 } p.x *= 1e-4 p.y *= 1e-4 let newBox = s2.Contour.BoundingBox.clone().translate(p) newBox.union(s1.Contour.BoundingBox) if (newBox.area < (s1.Contour.Area + s2.Contour.Area) * 1.3) { let partGroup = new PartGroup(this, partOther, i, j, p, newBox, bin) if (partGroup.RotatedStates.length > 0 && !arr.some(p => p.State.Contour === partGroup.State.Contour)// 类似的 ) { arr.push(partGroup) return arr } } } } } } return arr } // 添加网洞 AppendHole(path: Path) { let hole = new PartState() hole.Contour = PathGeneratorSingle.Allocate(path) PathGeneratorSingle.RegisterId(hole.Contour) hole.OrigionMinPoint = path.OrigionMinPoint hole.Rotation = 0 this._Holes.push(hole) if (GNestConfig.RotateHole) for (let i = 0; i < this.RotatedStates.length; i++) { let r = this.RotatedStates[i].Rotation let arr = this._RotateHoles[i] if (!arr) { arr = [] this._RotateHoles[i] = arr } if (r === 0) { hole.MinPoint = path.OrigionMinPoint arr.push(hole) } else { let newPath = new Path(path.Points, r) let newHole = new PartState() newHole.Rotation = r newHole.Contour = PathGeneratorSingle.Allocate(newPath) PathGeneratorSingle.RegisterId(newHole.Contour) newHole.OrigionMinPoint = newPath.OrigionMinPoint // 计算正确的最小点 let tempPath = new Path(path.OrigionPoints, r) newHole.MinPoint = tempPath.OrigionMinPoint arr.push(newHole) } } } // TODO:因为现在实现的是左右翻转,所以会出现角度匹配不完全的问题(缺失上下翻转) Mirror(doubleFace: boolean) { let states = this.RotatedStates let holes = this._Holes let roholes = this._RotateHoles if (!doubleFace) { this.RotatedStates = [] this._Holes = [] this._RotateHoles = [] } let count = states.length for (let i = 0; i < count; i++) { let s = states[i] let ns = s.Mirror() if (ns && !this.RotatedStates.some(state => state.Contour === s.Contour)) { this.RotatedStates.push(ns) if (this._Holes.length > 0) this._Holes.push(holes[i].Mirror()) if (this._RotateHoles.length > 0) this._RotateHoles.push(roholes[i].map(s => s.Mirror())) } } } // 浅克隆 Clone() { let part = new Part() part.Id = this.Id part.UserData = this.UserData part.RotatedStates = this.RotatedStates part.StateIndex = this.StateIndex part._Holes = this._Holes part._RotateHoles = this._RotateHoles return part } // 旋转起来,改变自身旋转状态(变异) Mutate(): this { this.StateIndex = RandomIndex(this.RotatedStates.length, this.StateIndex) return this } // #region -------------------------File------------------------- ReadFile(file: NestFiler) { this.Id = file.Read() let count = file.Read() as number this.RotatedStates = [] for (let i = 0; i < count; i++) { let state = new PartState() state.ReadFile(file) this.RotatedStates.push(state) } // 无旋转网洞 count = file.Read() this._Holes = [] for (let i = 0; i < count; i++) { let state = new PartState() state.ReadFile(file) this._Holes.push(state) } // 旋转网洞 count = file.Read() this._RotateHoles = [] for (let i = 0; i < count; i++) { let count2 = file.Read() as number let holes: PartState[] = [] for (let j = 0; j < count2; j++) { let state = new PartState() state.ReadFile(file) holes.push(state) } this._RotateHoles.push(holes) } } WriteFile(file: NestFiler) { file.Write(this.Id) file.Write(this.RotatedStates.length) for (let state of this.RotatedStates) state.WriteFile(file) // 非旋转网洞 file.Write(this._Holes.length) for (let hole of this._Holes) hole.WriteFile(file) // 写入旋转网洞 file.Write(this._RotateHoles.length) for (let holes of this._RotateHoles) { file.Write(holes.length) for (let hole of holes) { hole.WriteFile(file) } } } // #endregion } // 零件组合 export class PartGroup extends Part { constructor(public part1: Part, public part2: Part, public index1: number, public index2: number, public p: Point, public box: Box2, bin: Path, ) { super() let size = box.getSize(new Vector2()) this.Init2(NestCache.CreatePath(size.x, size.y, 0), bin, [0]) } Export(): Part[] { this.part1.StateIndex = this.index1 this.part2.StateIndex = this.index2 this.part1.PlacePosition = { x: this.PlacePosition.x - this.box.min.x * 1e4, y: this.PlacePosition.y - this.box.min.y * 1e4, } this.part2.PlacePosition = { x: this.PlacePosition.x - this.box.min.x * 1e4 + this.p.x * 1e4, y: this.PlacePosition.y - this.box.min.y * 1e4 + this.p.y * 1e4, } return [this.part1, this.part2] } }