diff --git a/src/Nest/Container.ts b/src/Nest/Container.ts index 63ef46d77..45523a899 100644 --- a/src/Nest/Container.ts +++ b/src/Nest/Container.ts @@ -5,19 +5,22 @@ import { Point } from "./Point"; import { Clipper, PolyType, Paths, ClipType, PolyFillType } from "clipper-js-fpoint"; import { arrayRemoveIf } from "../Common/ArrayExt"; import { equaln, AsVector3 } from "../Geometry/GeUtils"; +import { NestCache } from "./NestCache"; export class Container { Holes: Map = new Map(); //放置状态 PlacedArea = 0; - PlacedBox: Box3; + PlacedBox: Box3 = new Box3(); Placed: Part[] = []; OCS: Matrix4 = new Matrix4(); - constructor(public Path?: Path) + StatusKey: string; + constructor(private Path: Path) { + this.StatusKey = this.Path.Id.toString(); } get PlacedAll() @@ -52,6 +55,17 @@ export class Container } } + let cacheKey = this.StatusKey + "," + part.State.PlaceOutline.Id; + //读取缓存位置 + if (NestCache.PositionCache.has(cacheKey)) + { + NestCache.count1++; + let p = NestCache.PositionCache.get(cacheKey); + part.PlacePosition = p; + this.AppendPartHoles(part); + return true; + } + //------------无法容纳------------ if (this.Path.Area - this.PlacedArea < part.State.PlaceOutline.Area) return false; @@ -62,38 +76,43 @@ export class Container //------------首个------------ if (this.Placed.length === 0) { - let farLeftP = GetFarLeftP(binNfp); - part.PlacePosition = farLeftP; - - this.Placed.push(part); - this.PlacedArea += part.State.PlaceOutline.Area; - this.PlacedBox = part.State.PlaceOutline.BoundingBox.clone().translate(AsVector3(farLeftP)); + part.PlacePosition = GetFarLeftP(binNfp); this.AppendPartHoles(part); return true; } - //------------计算nfp------------ - //合并(零件和所有已经放置零件的NFP) - let clipper = new Clipper(); - for (let placedPart of this.Placed) + let combinedNfp: Paths = NestCache.NFPCache.get(cacheKey); + if (!combinedNfp) { - let nfp = placedPart.State.PlaceOutline.GetOutsideNFP(part.State.PlaceOutline); - if (!nfp) - return false; - let x = placedPart.PlacePosition.x; - let y = placedPart.PlacePosition.y; - for (let n of nfp) + combinedNfp = []; + NestCache.NFPCache.set(cacheKey, []); + //------------计算nfp------------ + //合并(零件和所有已经放置零件的NFP) + let clipper = new Clipper(); + for (let placedPart of this.Placed) { - let f = n.map(v => { return { x: v.x + x, y: v.y + y }; }); - clipper.AddPath(f, PolyType.ptSubject, true); + let nfp = placedPart.State.PlaceOutline.GetOutsideNFP(part.State.PlaceOutline); + if (!nfp) + return false; + let x = placedPart.PlacePosition.x; + let y = placedPart.PlacePosition.y; + for (let n of nfp) + { + let f = n.map(v => { return { x: v.x + x, y: v.y + y }; }); + clipper.AddPath(f, PolyType.ptSubject, true); + } } + + if (!clipper.Execute(ClipType.ctUnion, combinedNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero)) + return false; } + else + NestCache.count2++; + + if (combinedNfp.length === 0) return false; - let combinedNfp: Paths = []; - if (!clipper.Execute(ClipType.ctUnion, combinedNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero)) - return false; //binNfp 减去 combinedNfp 得到最终的nfp - clipper = new Clipper(); + let clipper = new Clipper(); clipper.AddPaths(binNfp, PolyType.ptSubject, true); clipper.AddPaths(combinedNfp, PolyType.ptClip, true); let finalNfp: Paths = []; @@ -110,6 +129,9 @@ export class Container if (finalNfp.length === 0) return false; + //缓存combinedNfp + NestCache.NFPCache.set(cacheKey, combinedNfp); + /** * 选择零件放置后的最优占地空间(box). * 使用凸包看起来不是最佳的 @@ -149,9 +171,6 @@ export class Container if (translate) { part.PlacePosition = translate; - this.Placed.push(part); - this.PlacedBox.copy(minBox); - this.PlacedArea += part.State.PlaceOutline.Area; this.AppendPartHoles(part); return true; } @@ -161,6 +180,14 @@ export class Container private AppendPartHoles(part: Part) { + this.Placed.push(part); + this.PlacedArea += part.State.PlaceOutline.Area; + this.PlacedBox.union(part.State.PlaceOutline.BoundingBox.clone().translate(AsVector3(part.PlacePosition))); + + //位置缓存 + this.StatusKey += "," + part.State.PlaceOutline.Id; + NestCache.PositionCache.set(this.StatusKey, part.PlacePosition); + part.Container = this; let holes: Container[] = []; for (let h of part.Holes) diff --git a/src/Nest/Converter/ConverBoard2Part.ts b/src/Nest/Converter/ConverBoard2Part.ts index 88bef1411..9993f04cf 100644 --- a/src/Nest/Converter/ConverBoard2Part.ts +++ b/src/Nest/Converter/ConverBoard2Part.ts @@ -8,8 +8,10 @@ import { Part } from "../Part"; import { Path } from "../Path"; import { Point } from "../Point"; import { Circle2Points } from "./Curves2Parts"; +import { NestCache } from "../NestCache"; export let DefaultBin = new Path([{ x: 0, y: 0 }, { x: 1220, y: 0 }, { x: 1220, y: 2440 }, { x: 0, y: 2440 }]); +DefaultBin.Id = -2; let Rotations = [ [0, Math.PI], @@ -71,9 +73,15 @@ export function Polylin2Points(pl: Polyline, outside: boolean, knifRadius: numbe export function ConverBoard2Part(board: Board): Part { let part = new Part(); - let [, pts] = Curves2Points(board.ContourCurve as Polyline, true, 3); + let path: Path; part.UserData = [board.ContourCurve.Clone()]; - let path = new Path(pts); + if (!board.IsSpecialShape) + path = NestCache.CreatePath(board.Width, board.Height); + else + { + let [, pts] = Curves2Points(board.ContourCurve as Polyline, true, 3); + path = new Path(pts); + } part.Init2(path, DefaultBin, Rotations[board.BoardProcessOption.lines]); for (let m of board.BoardModeling) { diff --git a/src/Nest/Individual.ts b/src/Nest/Individual.ts index c8c4bf531..3942a256c 100644 --- a/src/Nest/Individual.ts +++ b/src/Nest/Individual.ts @@ -1,9 +1,8 @@ -import { Part } from "./Part"; -import { RandomIndex } from "./Random"; +import { arrayLast } from "../Common/ArrayExt"; import { Container } from "./Container"; +import { Part } from "./Part"; import { Path } from "./Path"; -import { arrayLast } from "../Common/ArrayExt"; -import { Sleep } from "../Common/Sleep"; +import { RandomIndex } from "./Random"; /** * 个体 @@ -20,7 +19,7 @@ export class Individual { //强壮程度 越低越好 Fitness: number; - constructor(public Parts?: Part[], public mutationRate = 0.8) + constructor(public Parts?: Part[], public mutationRate = 0.5) { } @@ -36,26 +35,34 @@ export class Individual */ Mutate() { - for (let i = 0; i < this.Parts.length; i++) - { - let rand = Math.random(); - if (rand < this.mutationRate * 0.2) + if (this.mutationRate > 0.5) + for (let i = 0; i < this.Parts.length; i++) { - //和下一个调换顺序 - let j = i + 1; - if (j < this.Parts.length) - [this.Parts[i], this.Parts[j]] = [this.Parts[j], this.Parts[i]]; + let rand = Math.random(); + if (rand < this.mutationRate * 0.5) + { + //和下一个调换顺序 + let j = i + 1; + if (j < this.Parts.length) + [this.Parts[i], this.Parts[j]] = [this.Parts[j], this.Parts[i]]; + } + if (rand < this.mutationRate * 0.3) + { + this.Parts[i].Rotate(); + break; + } } - if (rand < this.mutationRate) - this.Parts[i].Rotate(); - } + + let index = RandomIndex(this.Parts.length); + this.Parts[index].Rotate(); //洗牌 let rand = Math.random(); if (rand < this.mutationRate) { let index = RandomIndex(this.Parts.length - 2); - let count = RandomIndex(this.Parts.length - 2 - index); + let count = Math.ceil(RandomIndex(this.Parts.length - 2 - index) * this.mutationRate * 2); + count = Math.ceil(count * this.mutationRate * 2); if (count > 0) { let parts = this.Parts.splice(index, count); @@ -63,7 +70,7 @@ export class Individual } } - if (this.mutationRate > 0.1) + if (this.mutationRate > 0.2) this.mutationRate -= 0.01; return this; } @@ -80,7 +87,7 @@ export class Individual { if (this.Containers.length > bestCount) { - this.Fitness = this.Containers.length + 2; + this.Fitness = Math.ceil(bestCount) + 1; return; }; let container = new Container(bin); diff --git a/src/Nest/NestCache.ts b/src/Nest/NestCache.ts new file mode 100644 index 000000000..41b56a9ee --- /dev/null +++ b/src/Nest/NestCache.ts @@ -0,0 +1,54 @@ +import { Path } from "./Path"; +import { Point } from "./Point"; + +export class NestCache +{ + static count1 = 0; + static count2 = 0; + static count3 = 0; + + private static _pathId = -1; + static get PathId() + { + return this._pathId++; + } + /** + * + */ + + static PositionCache = new Map(); + static NFPCache = new Map(); + + + private static CacheRect = new Map(); + + /** + * 用于创建原点在0点的矩形路径 + */ + static CreatePath(x: number, y: number): Path + { + let key = `${x.toFixed(2)},${y.toFixed(2)}`; + let path = this.CacheRect.get(key); + if (path) return path; + path = new Path([ + { x: 0, y: 0 }, + { x: x, y: 0 }, + { x: x, y: y }, + { x: 0, y: y }, + ]); + this.CacheRect.set(key, path); + return path; + } + + static Clear() + { + this.count1 = 0; + this.count2 = 0; + this.count3 = 0; + this._pathId = -1; + this.CacheRect.clear(); + this.PositionCache.clear(); + this.NFPCache.clear(); + } +} + diff --git a/src/Nest/OptimizeMachine.ts b/src/Nest/OptimizeMachine.ts index af5ee4865..0b84865a3 100644 --- a/src/Nest/OptimizeMachine.ts +++ b/src/Nest/OptimizeMachine.ts @@ -2,6 +2,7 @@ import { Path } from "./Path"; import { Part } from "./Part"; import { Individual } from "./Individual"; import { Sleep } from "../Common/Sleep"; +import { NestCache } from "./NestCache"; /** * 优化器 @@ -41,7 +42,9 @@ export class OptimizeMachine //启动 async Start() { + console.log(this.Parts.length); this._IsSuspend = false; + NestCache.Clear(); //1.初始化种群(n*个体) this._Individuals = [ @@ -65,12 +68,14 @@ export class OptimizeMachine //2.执行 await this.Run(); } + + calcCount = 0; + best = Infinity; + bestCount = 0; private async Run() { + console.time("1"); if (this.Parts.length === 0) return; - - let best = Infinity; - let calcCount = 0; //开始自然选择 while (!this._IsSuspend) //实验停止信号 { @@ -79,11 +84,12 @@ export class OptimizeMachine let badV = -Infinity; let badIndex: number; + // console.time(); //1.适应环境(放置零件) for (let i = 0; i < this._Individuals.length; i++) { let p = this._Individuals[i]; - await p.Evaluate(this.Bin, best); + await p.Evaluate(this.Bin, this.best); if (p.Fitness < bestV) { bestV = p.Fitness; @@ -94,37 +100,46 @@ export class OptimizeMachine badV = p.Fitness; badIndex = i; } - await Sleep(0); } - calcCount += this._Individuals.length; - console.log(calcCount); + // console.timeEnd(); + await Sleep(0); + this.calcCount += this._Individuals.length; //自然选择 this._Individuals.push(bestP.Clone());//繁衍它 this._Individuals.splice(badIndex, 1);//杀死它 - //除了最好的,其他人全部突变 - for (let p of this._Individuals) + + if (this.calcCount - this.bestCount > 8000) { - if (p !== bestP && p.Fitness !== undefined) - p.Mutate(); + for (let i of this._Individuals) + i.mutationRate = 0.5; } //回调最好的 - if (bestP.Fitness < best) + if (bestP.Fitness < this.best) { - console.log(bestP.Fitness); - best = bestP.Fitness; + this.best = bestP.Fitness; + console.timeLog("1", this.best, this.calcCount, NestCache.count1, NestCache.count2); if (this.callBack) { await this.callBack(bestP); } } + + //全部突变 + for (let p of this._Individuals) + { + if (p.Fitness !== undefined) + p.Mutate(); + } } } //暂停 Suspend() { + console.timeEnd("1"); + console.log(this.best, this.calcCount, NestCache.count1, NestCache.count2); this._IsSuspend = true; console.log("暂停"); } diff --git a/src/Nest/Part.ts b/src/Nest/Part.ts index 64d1863a6..8621407b5 100644 --- a/src/Nest/Part.ts +++ b/src/Nest/Part.ts @@ -30,11 +30,7 @@ import { AsVector3, equaln } from "../Geometry/GeUtils"; */ export class Part { - Container: Container; - - //绑定数据 - id: number; //应该也是可序列化的实体 UserData: Curve[]; @@ -96,6 +92,7 @@ export class Part return this; } + Init2(outline: Path, bin?: Path, rotations: number[] = []) { //初始化零件的状态集合 @@ -123,24 +120,17 @@ export class Part 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; return part; } + //旋转起来,改变自身旋转状态(变异) Rotate(): this { this.StateIndex = RandomIndex(this.RotatedStates.length); return this; } - - ApplyPlace(containerCS: Matrix4)//容器的放置矩阵 - { - let m = this.PlaceCS; - // m.multiplyMatrices(containerCS, m); - //ent.applyMatrix4(m); - } } diff --git a/src/Nest/Path.ts b/src/Nest/Path.ts index 3ce500bc0..8aa65d55b 100644 --- a/src/Nest/Path.ts +++ b/src/Nest/Path.ts @@ -6,6 +6,7 @@ import { ISerialize } from "../DatabaseServices/ISerialize"; import { AsVector2, AsVector3, equaln } from "../Geometry/GeUtils"; import { OffsetPolyline } from "../GraphicsSystem/OffsetPolyline"; import { Point } from "./Point"; +import { NestCache } from "./NestCache"; /** * 轮廓路径类 @@ -160,6 +161,18 @@ export class Path implements ISerialize return nfps; } + private _id: number; + get Id(): number + { + if (this._id === undefined) + this._id = NestCache.PathId; + return this._id; + } + set Id(v) + { + this._id = v; + } + protected _Polyline: Polyline; get Polyline(): Polyline { diff --git a/src/Nest/Test/TestPlace.ts b/src/Nest/Test/TestPlace.ts index 314bba6e3..303ba14ac 100644 --- a/src/Nest/Test/TestPlace.ts +++ b/src/Nest/Test/TestPlace.ts @@ -37,9 +37,8 @@ export class Command_TestPlace implements Command map.set(pl, pl.OCS); } - let container = new Container(); + let container = new Container(binPath); container.OCS = new Matrix4().setPosition(bp); - container.Path = binPath; for (let i = 0; i < parts.length; i++) { let p = parts[i];