using ClipperLib; using Minkowski; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace DeepNestLib { public class Background { public static bool EnableCaches = true; object lockobj; public Background() { lockobj = new object(); } public static NFP shiftPolygon(NFP p, PlacementItem shift) { NFP shifted = new NFP(); for (var i = 0; i < p.length; i++) { shifted.AddPoint(new SvgPoint(p[i].x + shift.x, p[i].y + shift.y) { exact = p[i].exact }); } if (p.children != null /*&& p.children.Count*/) { shifted.children = new List(); for (int i = 0; i < p.children.Count; i++) { shifted.children.Add(shiftPolygon(p.children[i], shift)); } } return shifted; } // returns the square of the length of any merged lines // filter out any lines less than minlength long public static MergedResult mergedLength(NFP[] parts, NFP p, double minlength, double tolerance) { // var min2 = minlength * minlength; // var totalLength = 0; // var segments = []; // for (var i = 0; i < p.length; i++) // { // var A1 = p[i]; // if (i + 1 == p.length) // { // A2 = p[0]; // } // else // { // var A2 = p[i + 1]; // } // if (!A1.exact || !A2.exact) // { // continue; // } // var Ax2 = (A2.x - A1.x) * (A2.x - A1.x); // var Ay2 = (A2.y - A1.y) * (A2.y - A1.y); // if (Ax2 + Ay2 < min2) // { // continue; // } // var angle = Math.atan2((A2.y - A1.y), (A2.x - A1.x)); // var c = Math.cos(-angle); // var s = Math.sin(-angle); // var c2 = Math.cos(angle); // var s2 = Math.sin(angle); // var relA2 = { x: A2.x - A1.x, y: A2.y - A1.y}; // var rotA2x = relA2.x * c - relA2.y * s; // for (var j = 0; j < parts.length; j++) // { // var B = parts[j]; // if (B.length > 1) // { // for (var k = 0; k < B.length; k++) // { // var B1 = B[k]; // if (k + 1 == B.length) // { // var B2 = B[0]; // } // else // { // var B2 = B[k + 1]; // } // if (!B1.exact || !B2.exact) // { // continue; // } // var Bx2 = (B2.x - B1.x) * (B2.x - B1.x); // var By2 = (B2.y - B1.y) * (B2.y - B1.y); // if (Bx2 + By2 < min2) // { // continue; // } // // B relative to A1 (our point of rotation) // var relB1 = { x: B1.x - A1.x, y: B1.y - A1.y}; // var relB2 = { x: B2.x - A1.x, y: B2.y - A1.y}; // // rotate such that A1 and A2 are horizontal // var rotB1 = { x: relB1.x* c -relB1.y * s, y: relB1.x* s +relB1.y * c}; // var rotB2 = { x: relB2.x* c -relB2.y * s, y: relB2.x* s +relB2.y * c}; // if(!GeometryUtil.almostEqual(rotB1.y, 0, tolerance) || !GeometryUtil.almostEqual(rotB2.y, 0, tolerance)){ // continue; // } // var min1 = Math.min(0, rotA2x); // var max1 = Math.max(0, rotA2x); // var min2 = Math.min(rotB1.x, rotB2.x); // var max2 = Math.max(rotB1.x, rotB2.x); // // not overlapping // if(min2 >= max1 || max2 <= min1){ // continue; // } // var len = 0; // var relC1x = 0; // var relC2x = 0; // // A is B // if(GeometryUtil.almostEqual(min1, min2) && GeometryUtil.almostEqual(max1, max2)){ // len = max1-min1; // relC1x = min1; // relC2x = max1; // } // // A inside B // else if(min1 > min2 && max1 min1 && max2 min2){ // totalLength += len; // var relC1 = { x: relC1x * c2, y: relC1x * s2 }; //var relC2 = { x: relC2x * c2, y: relC2x * s2 }; //var C1 = { x: relC1.x + A1.x, y: relC1.y + A1.y }; //var C2 = { x: relC2.x + A1.x, y: relC2.y + A1.y }; //segments.push([C1, C2]); // } // } // } // if(B.children && B.children.length > 0){ // var child = mergedLength(B.children, p, minlength, tolerance); //totalLength += child.totalLength; // segments = segments.concat(child.segments); // } // } // } // return {totalLength: totalLength, segments: segments}; throw new NotImplementedException(); } public class MergedResult { public double totalLength; public object segments; } public static NFP[] cloneNfp(NFP[] nfp, bool inner = false) { if (!inner) { return new[] { clone(nfp.First()) }; } // inner nfp is actually an array of nfps int nCount = nfp.Length; NFP[] newnfps = new NFP[nCount]; //List newnfp = new List(); for (var i = 0; i < nCount; i++) { newnfps[i] = clone(nfp[i]); //newnfp.Add(clone(nfp[i])); } return newnfps;// newnfp.ToArray(); } public static NFP clone(NFP nfp) { NFP newnfp = new NFP(); newnfp.source = nfp.source; for (var i = 0; i < nfp.length; i++) { newnfp.AddPoint(new SvgPoint(nfp[i].x, nfp[i].y)); } if (nfp.children != null && nfp.children.Count > 0) { newnfp.children = new List(); for (int i = 0; i < nfp.children.Count; i++) { var child = nfp.children[i]; NFP newchild = new NFP(); for (var j = 0; j < child.length; j++) { newchild.AddPoint(new SvgPoint(child[j].x, child[j].y)); } newnfp.children.Add(newchild); } } return newnfp; } public int callCounter = 0; public Dictionary cacheProcess = new Dictionary(); public NFP[] Process2(NFP A, NFP B, int type) { var key = A.source + ";" + B.source + ";" + A.rotation + ";" + B.rotation; bool cacheAllow = type != 1; if (cacheProcess.ContainsKey(key) && cacheAllow) { return cacheProcess[key]; } Stopwatch swg = Stopwatch.StartNew(); Dictionary> dic1 = new Dictionary>(); Dictionary> dic2 = new Dictionary>(); dic2.Add("A", new List()); foreach (var item in A.Points) { var target = dic2["A"]; target.Add(item.x); target.Add(item.y); } dic2.Add("B", new List()); foreach (var item in B.Points) { var target = dic2["B"]; target.Add(item.x); target.Add(item.y); } List hdat = new List(); foreach (var item in A.children) { foreach (var pitem in item.Points) { hdat.Add(pitem.x); hdat.Add(pitem.y); } } var aa = dic2["A"]; var bb = dic2["B"]; var arr1 = A.children.Select(z => z.Points.Count() * 2).ToArray(); MinkowskiWrapper.setData(aa.Count, aa.ToArray(), A.children.Count, arr1, hdat.ToArray(), bb.Count, bb.ToArray()); MinkowskiWrapper.calculateNFP(); callCounter++; int[] sizes = new int[2]; MinkowskiWrapper.getSizes1(sizes); int[] sizes1 = new int[sizes[0]]; int[] sizes2 = new int[sizes[1]]; MinkowskiWrapper.getSizes2(sizes1, sizes2); double[] dat1 = new double[sizes1.Sum()]; double[] hdat1 = new double[sizes2.Sum()]; MinkowskiWrapper.getResults(dat1, hdat1); if (sizes1.Count() > 1) { throw new ArgumentException("sizes1 cnt >1"); } //convert back to answer here bool isa = true; List Apts = new List(); List> holesval = new List>(); bool holes = false; for (int i = 0; i < dat1.Length; i += 2) { var x1 = (float)dat1[i]; var y1 = (float)dat1[i + 1]; Apts.Add(new PointF(x1, y1)); } int index = 0; for (int i = 0; i < sizes2.Length; i++) { holesval.Add(new List()); for (int j = 0; j < sizes2[i]; j++) { holesval.Last().Add(hdat1[index]); index++; } } List> holesout = new List>(); foreach (var item in holesval) { holesout.Add(new List()); for (int i = 0; i < item.Count; i += 2) { var x = (float)item[i]; var y = (float)item[i + 1]; holesout.Last().Add(new PointF(x, y)); } } NFP ret = new NFP(); ret.Points = new List() { }; foreach (var item in Apts) { ret.AddPoint(new SvgPoint(item.X, item.Y)); } foreach (var item in holesout) { if (ret.children == null) ret.children = new List(); ret.children.Add(new NFP()); ret.children.Last().Points = new List() { }; foreach (var hitem in item) { ret.children.Last().AddPoint(new SvgPoint(hitem.X, hitem.Y)); } } swg.Stop(); var msg = swg.ElapsedMilliseconds; var res = new NFP[] { ret }; if (cacheAllow) { cacheProcess.Add(key, res); } return res; } public static NFP getFrame(NFP A) { var bounds = GeometryUtil.getPolygonBounds(A); // expand bounds by 10% bounds.width *= 1.1; bounds.height *= 1.1; bounds.x -= 0.5 * (bounds.width - (bounds.width / 1.1)); bounds.y -= 0.5 * (bounds.height - (bounds.height / 1.1)); var frame = new NFP(); frame.push(new SvgPoint(bounds.x, bounds.y)); frame.push(new SvgPoint(bounds.x + bounds.width, bounds.y)); frame.push(new SvgPoint(bounds.x + bounds.width, bounds.y + bounds.height)); frame.push(new SvgPoint(bounds.x, bounds.y + bounds.height)); frame.children = new List() { (NFP)A }; frame.source = A.source; frame.rotation = 0; return frame; } public NFP[] getInnerNfp(NFP A, NFP B, int type, SvgNestConfig config) { if (A.source != null && B.source != null) { var key = new DbCacheKey() { A = A.source.Value, B = B.source.Value, ARotation = 0, BRotation = B.rotation, //Inside =true?? }; //var doc = window.db.find({ A: A.source, B: B.source, Arotation: 0, Brotation: B.rotation }, true); var res = window.db.find(key, true); if (res != null) { return res; } } var frame = getFrame(A); var nfp = getOuterNfp(frame, B, type, true); if (nfp == null || nfp.children == null || nfp.children.Count == 0) { return null; } List holes = new List(); if (A.children != null && A.children.Count > 0) { holes = (A.children ?? Enumerable.Empty()) // 根据 A.children 的实际类型替换 YourChildType .AsParallel().AsOrdered() .WithExecutionMode(ParallelExecutionMode.ForceParallelism) .WithDegreeOfParallelism(Math.Min(Environment.ProcessorCount - 1, 8)) .Select(child => getOuterNfp(child, B, 1)) .Where(hnfp => hnfp != null) .ToList(); } //if (A.children != null && A.children.Count > 0) //{ // for (var i = 0; i < A.children.Count; i++) // { // var hnfp = getOuterNfp(A.children[i], B, 1); // if (hnfp != null) // { // holes.Add(hnfp); // } // } //} if (holes.Count == 0) { return nfp.children.ToArray(); } var clipperNfp = innerNfpToClipperCoordinates(nfp.children.ToArray(), config.clipperScale); var clipperHoles = innerNfpToClipperCoordinates(holes.ToArray(), config.clipperScale); List> finalNfp = new List>(); var clipper = new ClipperLib.Clipper(); clipper.AddPaths(clipperHoles.Select(z => z.ToList()).ToList(), ClipperLib.PolyType.ptClip, true); clipper.AddPaths(clipperNfp.Select(z => z.ToList()).ToList(), ClipperLib.PolyType.ptSubject, true); if (!clipper.Execute(ClipperLib.ClipType.ctDifference, finalNfp, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero)) { return nfp.children.ToArray(); } if (finalNfp.Count == 0) { return null; } List f = new List(); for (var i = 0; i < finalNfp.Count; i++) { f.Add(toNestCoordinates(finalNfp[i].ToArray(), config.clipperScale)); } if (A.source != null && B.source != null) { // insert into db //console.log('inserting inner: ', A.source, B.source, B.rotation, f); var doc = new DbCacheKey() { A = A.source.Value, B = B.source.Value, ARotation = 0, BRotation = B.rotation, nfp = f.ToArray() }; window.db.insert(doc, true); } return f.ToArray(); } public static NFP rotatePolygon(NFP polygon, float degrees) { NFP rotated = new NFP(); var angle = degrees * Math.PI / 180; List pp = new List(); for (var i = 0; i < polygon.length; i++) { var x = polygon[i].x; var y = polygon[i].y; var x1 = (x * Math.Cos(angle) - y * Math.Sin(angle)); var y1 = (x * Math.Sin(angle) + y * Math.Cos(angle)); pp.Add(new SvgPoint(x1, y1)); } rotated.Points = pp; if (polygon.children != null && polygon.children.Count > 0) { rotated.children = new List(); ; for (var j = 0; j < polygon.children.Count; j++) { rotated.children.Add(rotatePolygon(polygon.children[j], degrees)); } } return rotated; } public SheetPlacement placeParts(NFP[] sheets, NFP[] parts, SvgNestConfig config, int nestindex) { if (sheets == null || sheets.Count() == 0) return null; NFP[] allParts = new NFP[parts.Length]; Array.Copy(parts, allParts, parts.Length); int i, j, k, m, n; double totalsheetarea = 0; NFP part = null; // total length of merged lines double totalMerged = 0; // rotate paths by given rotation var rotated = new List(); for (i = 0; i < parts.Length; i++) { var r = rotatePolygon(parts[i], parts[i].rotation); r.Rotation = parts[i].rotation; r.source = parts[i].source; r.id = parts[i].id; r.Name = parts[i].Name; rotated.Add(r); } parts = rotated.ToArray(); List allplacements = new List(); double fitness = 0; string key; NFP nfp; double sheetarea = -1; int totalPlaced = 0; int totalParts = parts.Count(); float angleIncrement = 360f / config.rotations; int maxAttempts = (int)(360f / config.rotations); while (parts.Length > 0) { List placed = new List(); List placements = new List(); // open a new sheet var sheet = sheets.First(); sheets = sheets.Skip(1).ToArray(); sheetarea = Math.Abs(GeometryUtil.polygonArea(sheet)); totalsheetarea += sheetarea; //fitness += sheetarea; // add 1 for each new sheet opened (lower fitness is better) string clipkey = ""; Dictionary clipCache = new Dictionary(); var clipper = new ClipperLib.Clipper(); var combinedNfp = new List>(); var error = false; IntPoint[][] clipperSheetNfp = null; double? minwidth = null; PlacementItem position = null; double? minarea = null; for (i = 0; i < parts.Length; i++) { float prog = 0.66f + 0.34f * (totalPlaced / (float)totalParts); DisplayProgress(prog); part = parts[i]; // inner NFP NFP[] sheetNfp = null; //Tuple bestNfp = null; //Parallel.For(0, maxAttempts, (index, state) => //{// 检查是否已请求停止 // if (state.IsStopped) // { // return; // } // NFP[] sheetNfpBest = getInnerNfp(sheet, part, 0, config); // // 检查是否已请求停止 // if (state.IsStopped) // { // return; // } // if (sheetNfpBest != null && sheetNfpBest.Length > 0) // { // bestNfp = new Tuple(index, sheetNfpBest); // state.Stop(); // return; // } //}); //if(bestNfp != null) //{ // var r = rotatePolygon(part, angleIncrement); // r.rotation = part.rotation + angleIncrement; // r.source = part.source; // r.id = part.id; // r.Name = part.Name; // // rotation is not in-place // part = r; // parts[i] = r; // if (part.rotation > 360f) // { // part.rotation = part.rotation % 360f; // } // sheetNfp = bestNfp.Item2; //} // try all possible rotations until it fits // (only do this for the first part of each sheet, to ensure that all parts that can be placed are, even if we have to to open a lot of sheets) for (j = 0; j <= maxAttempts; j++) { //Stopwatch sw = Stopwatch.StartNew(); sheetNfp = getInnerNfp(sheet, part, 0, config); //sw.Stop(); //Trace.WriteLine($"1: {sw.ElapsedMilliseconds}"); if (sheetNfp != null && sheetNfp.Length > 0) { if (sheetNfp[0].length == 0) { throw new ArgumentException(); } else { break; } } //Stopwatch sw2 = Stopwatch.StartNew(); //sw2.Start(); var r = rotatePolygon(part, angleIncrement); r.rotation = part.rotation + angleIncrement; r.source = part.source; r.id = part.id; r.Name = part.Name; // rotation is not in-place part = r; parts[i] = r; if (part.rotation > 360f) { part.rotation = part.rotation % 360f; } //sw2.Stop(); //Trace.WriteLine($"2: {sw2.ElapsedMilliseconds}"); } // part unplaceable, skip if (sheetNfp == null || sheetNfp.Length == 0) { continue; } position = null; if (placed.Count == 0) { // first placement, put it on the top left corner for (j = 0; j < sheetNfp.Length; j++) { for (k = 0; k < sheetNfp[j].length; k++) { if (position == null || ((sheetNfp[j][k].x - part[0].x) < position.x) || (GeometryUtil._almostEqual(sheetNfp[j][k].x - part[0].x, position.x) && ((sheetNfp[j][k].y - part[0].y) < position.y)) ) { position = new PlacementItem() { x = sheetNfp[j][k].x - part[0].x, y = sheetNfp[j][k].y - part[0].y, id = part.id, rotation = part.rotation, source = part.source.Value }; } } } if (position == null) { throw new Exception("position null"); //console.log(sheetNfp); } placements.Add(position); placed.Add(part); totalPlaced++; continue; } clipperSheetNfp = innerNfpToClipperCoordinates(sheetNfp, config.clipperScale); clipper = new ClipperLib.Clipper(); combinedNfp = new List>(); error = false; // check if stored in clip cache //var startindex = 0; clipkey = "s:" + part.source + "r:" + part.rotation; var startindex = 0; if (EnableCaches && clipCache.ContainsKey(clipkey)) { var prevNfp = clipCache[clipkey].nfpp; clipper.AddPaths(prevNfp.Select(z => z.ToList()).ToList(), ClipperLib.PolyType.ptSubject, true); startindex = clipCache[clipkey].index; } for (j = startindex; j < placed.Count; j++) { nfp = getOuterNfp(placed[j], part, 0); // minkowski difference failed. very rare but could happen if (nfp == null) { error = true; break; } // shift to placed location for (m = 0; m < nfp.length; m++) { nfp[m].x += placements[j].x; nfp[m].y += placements[j].y; } if (nfp.children != null && nfp.children.Count > 0) { for (n = 0; n < nfp.children.Count; n++) { for (var o = 0; o < nfp.children[n].length; o++) { nfp.children[n][o].x += placements[j].x; nfp.children[n][o].y += placements[j].y; } } } var clipperNfp = nfpToClipperCoordinates(nfp, config.clipperScale); clipper.AddPaths(clipperNfp.Select(z => z.ToList()).ToList(), ClipperLib.PolyType.ptSubject, true); } //TODO: a lot here to insert if (error || !clipper.Execute(ClipperLib.ClipType.ctUnion, combinedNfp, ClipperLib.PolyFillType.pftNonZero, ClipperLib.PolyFillType.pftNonZero)) { //console.log('clipper error', error); continue; } if (EnableCaches) { clipCache[clipkey] = new ClipCacheItem() { index = placed.Count - 1, nfpp = combinedNfp.Select(z => z.ToArray()).ToArray() }; } //console.log('save cache', placed.length - 1); // difference with sheet polygon List> _finalNfp = new List>(); clipper = new ClipperLib.Clipper(); clipper.AddPaths(combinedNfp, ClipperLib.PolyType.ptClip, true); clipper.AddPaths(clipperSheetNfp.Select(z => z.ToList()).ToList(), ClipperLib.PolyType.ptSubject, true); if (!clipper.Execute(ClipperLib.ClipType.ctDifference, _finalNfp, ClipperLib.PolyFillType.pftEvenOdd, ClipperLib.PolyFillType.pftNonZero)) { continue; } if (_finalNfp == null || _finalNfp.Count == 0) { continue; } List f = new List(); for (j = 0; j < _finalNfp.Count; j++) { // back to normal scale f.Add(Background.toNestCoordinates(_finalNfp[j].ToArray(), config.clipperScale)); } var finalNfp = f; //finalNfp = f; // choose placement that results in the smallest bounding box/hull etc // todo: generalize gravity direction /*var minwidth = null; var minarea = null; var minx = null; var miny = null; var nf, area, shiftvector;*/ minwidth = null; minarea = null; double? minx = null; double? miny = null; NFP nf; double area = 0; PlacementItem shiftvector = null; NFP allpoints = new NFP(); for (m = 0; m < placed.Count; m++) { for (n = 0; n < placed[m].length; n++) { allpoints.AddPoint( new SvgPoint( placed[m][n].x + placements[m].x, placed[m][n].y + placements[m].y)); } } PolygonBounds allbounds = null; PolygonBounds partbounds = null; if (config.placementType == PlacementTypeEnum.gravity || config.placementType == PlacementTypeEnum.box) { allbounds = GeometryUtil.getPolygonBounds(allpoints); NFP partpoints = new NFP(); for (m = 0; m < part.length; m++) { partpoints.AddPoint(new SvgPoint(part[m].x, part[m].y)); } partbounds = GeometryUtil.getPolygonBounds(partpoints); } else { allpoints = getHull(allpoints); } for (j = 0; j < finalNfp.Count; j++) { nf = finalNfp[j]; //console.log('evalnf',nf.length); for (k = 0; k < nf.length; k++) { shiftvector = new PlacementItem() { id = part.id, x = nf[k].x - part[0].x, y = nf[k].y - part[0].y, source = part.source.Value, rotation = part.rotation }; PolygonBounds rectbounds = null; if (config.placementType == PlacementTypeEnum.gravity || config.placementType == PlacementTypeEnum.box) { NFP poly = new NFP(); poly.AddPoint(new SvgPoint(allbounds.x, allbounds.y)); poly.AddPoint(new SvgPoint(allbounds.x + allbounds.width, allbounds.y)); poly.AddPoint(new SvgPoint(allbounds.x + allbounds.width, allbounds.y + allbounds.height)); poly.AddPoint(new SvgPoint(allbounds.x, allbounds.y + allbounds.height)); /* [ // allbounds points { x: allbounds.x, y: allbounds.y}, { x: allbounds.x + allbounds.width, y: allbounds.y}, { x: allbounds.x + allbounds.width, y: allbounds.y + allbounds.height}, { x: allbounds.x, y: allbounds.y + allbounds.height},*/ poly.AddPoint(new SvgPoint(partbounds.x + shiftvector.x, partbounds.y + shiftvector.y)); poly.AddPoint(new SvgPoint(partbounds.x + partbounds.width + shiftvector.x, partbounds.y + shiftvector.y)); poly.AddPoint(new SvgPoint(partbounds.x + partbounds.width + shiftvector.x, partbounds.y + partbounds.height + shiftvector.y)); poly.AddPoint(new SvgPoint(partbounds.x + shiftvector.x, partbounds.y + partbounds.height + shiftvector.y)); /* [ // part points { x: partbounds.x + shiftvector.x, y: partbounds.y + shiftvector.y}, { x: partbounds.x + partbounds.width + shiftvector.x, y: partbounds.y + shiftvector.y}, { x: partbounds.x + partbounds.width + shiftvector.x, y: partbounds.y + partbounds.height + shiftvector.y}, { x: partbounds.x + shiftvector.x, y: partbounds.y + partbounds.height + shiftvector.y} ]*/ rectbounds = GeometryUtil.getPolygonBounds(poly); // weigh width more, to help compress in direction of gravity if (config.placementType == PlacementTypeEnum.gravity) { area = rectbounds.width * 2 + rectbounds.height; } else { area = rectbounds.width * rectbounds.height; //// 修改单侧部井的权重 //if(part.Name!=null && part.Name == "单侧") //{ // area *= 0.2; //} } } else { // must be convex hull var localpoints = clone(allpoints); for (m = 0; m < part.length; m++) { localpoints.AddPoint(new SvgPoint(part[m].x + shiftvector.x, part[m].y + shiftvector.y)); } area = Math.Abs(GeometryUtil.polygonArea(getHull(localpoints))); shiftvector.hull = getHull(localpoints); shiftvector.hullsheet = getHull(sheet); } //console.timeEnd('evalbounds'); //console.time('evalmerge'); MergedResult merged = null; if (config.mergeLines) { throw new NotImplementedException(); // if lines can be merged, subtract savings from area calculation var shiftedpart = shiftPolygon(part, shiftvector); List shiftedplaced = new List(); for (m = 0; m < placed.Count; m++) { shiftedplaced.Add(shiftPolygon(placed[m], placements[m])); } // don't check small lines, cut off at about 1/2 in double minlength = 0.5 * config.scale; merged = mergedLength(shiftedplaced.ToArray(), shiftedpart, minlength, 0.1 * config.curveTolerance); area -= merged.totalLength * config.timeRatio; } //console.timeEnd('evalmerge'); if ( minarea == null || area < minarea || (GeometryUtil._almostEqual(minarea, area) && (minx == null || shiftvector.x < minx)) || (GeometryUtil._almostEqual(minarea, area) && (minx != null && GeometryUtil._almostEqual(shiftvector.x, minx) && shiftvector.y < miny)) ) { minarea = area; minwidth = rectbounds != null ? rectbounds.width : 0; position = shiftvector; if (minx == null || shiftvector.x < minx) { minx = shiftvector.x; } if (miny == null || shiftvector.y < miny) { miny = shiftvector.y; } if (config.mergeLines) { position.mergedLength = merged.totalLength; position.mergedSegments = merged.segments; } } } } if (position != null) { placed.Add(part); totalPlaced++; placements.Add(position); if (position.mergedLength != null) { totalMerged += position.mergedLength.Value; } } // send placement progress signal var placednum = placed.Count; for (j = 0; j < allplacements.Count; j++) { placednum += allplacements[j].sheetplacements.Count; } //console.log(placednum, totalnum); //ipcRenderer.send('background-progress', { index: nestindex, progress: 0.5 + 0.5 * (placednum / totalnum)}); //console.timeEnd('placement'); } //if (!minwidth.HasValue) //{ // fitness = double.NaN; //} //else //{ // fitness += (minwidth.Value / sheetarea) + minarea.Value; //} for (i = 0; i < placed.Count; i++) { var index = Array.IndexOf(parts, placed[i]); if (index >= 0) { parts = parts.splice(index, 1); } } if (placements != null && placements.Count > 0) { allplacements.Add(new SheetPlacementItem() { sheetId = sheet.id, sheetSource = sheet.source.Value, sheetplacements = placements }); //allplacements.Add({ sheet: sheet.source, sheetid: sheet.id, sheetplacements: placements}); } else { break; // something went wrong } if (sheets.Count() == 0) { break; } } // there were parts that couldn't be placed // scale this value high - we really want to get all the parts in, even at the cost of opening new sheets // 原适配率 //for (i = 0; i < parts.Count(); i++) //{ // fitness += 100000000 * (Math.Abs(GeometryUtil.polygonArea(parts[i])) / totalsheetarea); //} // 自定义的适配率 double dFitValue = 0; foreach(var sheet in allplacements) { foreach(PlacementItem item in sheet.sheetplacements) { NFP find = allParts.FirstOrDefault(it => it.id == item.id); if(find != null) { if (find.Name.Equals("双侧")) { dFitValue += 2.2; } else { dFitValue += 1.0; } } } } fitness = (totalParts*3.0 - dFitValue) / (totalParts*3.0); // send finish progerss signal //ipcRenderer.send('background-progress', { index: nestindex, progress: -1}); return new SheetPlacement() { placements = new[] { allplacements.ToList() }, fitness = fitness, // paths = paths, area = sheetarea, mergedLength = totalMerged }; //return { placements: allplacements, fitness: fitness, area: sheetarea, mergedLength: totalMerged }; } // jsClipper uses X/Y instead of x/y... public DataInfo data; NFP[] parts; int index; // run the placement synchronously public windowUnk window = new windowUnk(); public Action ResponseAction; public long LastPlacePartTime = 0; public void sync() { //console.log('starting synchronous calculations', Object.keys(window.nfpCache).length); //console.log('in sync'); var c = 0; foreach (var key in window.nfpCache) { c++; } //console.log('nfp cached:', c); Stopwatch sw = Stopwatch.StartNew(); var placement = placeParts(data.sheets.ToArray(), parts, data.config, index); sw.Stop(); LastPlacePartTime = sw.ElapsedMilliseconds; placement.index = data.index; ResponseAction(placement); //ipcRenderer.send('background-response', placement); } public void BackgroundStart(DataInfo data) { this.data = data; var index = data.index; var individual = data.individual; var parts = individual.placements; var rotations = individual.Rotation; var ids = data.ids; var sources = data.sources; var children = data.children; for (var i = 0; i < parts.Count; i++) { parts[i].rotation = rotations[i]; parts[i].id = ids[i]; parts[i].source = sources[i]; if (!data.config.simplify) { parts[i].children = children[i]; } } for (int i = 0; i < data.sheets.Count; i++) { data.sheets[i].id = data.sheetids[i]; data.sheets[i].source = data.sheetsources[i]; data.sheets[i].children = data.sheetchildren[i]; } // preprocess List pairs = new List(); if (Background.UseParallel) { object lobj = new object(); Parallel.For(0, parts.Count, i => { { var B = parts[i]; for (var j = 0; j < i; j++) { var A = parts[j]; var key = new NfpPair() { A = A, B = B, ARotation = A.rotation, BRotation = B.rotation, Asource = A.source.Value, Bsource = B.source.Value }; var doc = new DbCacheKey() { A = A.source.Value, B = B.source.Value, ARotation = A.rotation, BRotation = B.rotation }; lock (lobj) { if (!inpairs(key, pairs.ToArray()) && !window.db.has(doc)) { pairs.Add(key); } } } } }); } else { for (var i = 0; i < parts.Count; i++) { var B = parts[i]; for (var j = 0; j < i; j++) { var A = parts[j]; var key = new NfpPair() { A = A, B = B, ARotation = A.rotation, BRotation = B.rotation, Asource = A.source.Value, Bsource = B.source.Value }; var doc = new DbCacheKey() { A = A.source.Value, B = B.source.Value, ARotation = A.rotation, BRotation = B.rotation }; if (!inpairs(key, pairs.ToArray()) && !window.db.has(doc)) { pairs.Add(key); } } } } //console.log('pairs: ', pairs.length); //console.time('Total'); this.parts = parts.ToArray(); if (pairs.Count > 0) { var ret1 = pmapDeepNest(pairs); thenDeepNest(ret1, parts); } else { sync(); } } public NFP getPart(int source, List parts) { for (var k = 0; k < parts.Count; k++) { if (parts[k].source == source) { return parts[k]; } } return null; } public void thenIterate(NfpPair processed, List parts) { // returned data only contains outer nfp, we have to account for any holes separately in the synchronous portion // this is because the c++ addon which can process interior nfps cannot run in the worker thread var A = getPart(processed.Asource, parts); var B = getPart(processed.Bsource, parts); List Achildren = new List(); if (A.children != null) { for (int j = 0; j < A.children.Count; j++) { Achildren.Add(rotatePolygon(A.children[j], processed.ARotation)); } } if (Achildren.Count > 0) { var Brotated = rotatePolygon(B, processed.BRotation); var bbounds = GeometryUtil.getPolygonBounds(Brotated); List cnfp = new List(); for (int j = 0; j < Achildren.Count; j++) { var cbounds = GeometryUtil.getPolygonBounds(Achildren[j]); if (cbounds.width > bbounds.width && cbounds.height > bbounds.height) { var n = getInnerNfp(Achildren[j], Brotated, 1, data.config); if (n != null && n.Count() > 0) { cnfp.AddRange(n); } } } processed.nfp.children = cnfp; } DbCacheKey doc = new DbCacheKey() { A = processed.Asource, B = processed.Bsource, ARotation = processed.ARotation, BRotation = processed.BRotation, nfp = new[] { processed.nfp } }; /*var doc = { A: processed[i].Asource, B: processed[i].Bsource, Arotation: processed[i].Arotation, Brotation: processed[i].Brotation, nfp: processed[i].nfp };*/ window.db.insert(doc); } public Action DisplayProgressAction { get; set; } public void DisplayProgress(float p) { DisplayProgressAction?.Invoke(p); } public void thenDeepNest(NfpPair[] processed, List parts) { int cnt = 0; if (UseParallel) { Parallel.For(0, processed.Count(), (i) => { float progress = 0.33f + 0.33f * (cnt / (float)processed.Count()); cnt++; DisplayProgress(progress); thenIterate(processed[i], parts); }); } else { for (var i = 0; i < processed.Count(); i++) { float progress = 0.33f + 0.33f * (cnt / (float)processed.Count()); cnt++; DisplayProgress(progress); thenIterate(processed[i], parts); } } //console.timeEnd('Total'); //console.log('before sync'); sync(); } public bool inpairs(NfpPair key, NfpPair[] p) { for (var i = 0; i < p.Length; i++) { if (p[i].Asource == key.Asource && p[i].Bsource == key.Bsource && p[i].ARotation == key.ARotation && p[i].BRotation == key.BRotation) { return true; } } return false; } public static bool UseParallel = false; public NfpPair[] pmapDeepNest(List pairs) { NfpPair[] ret = new NfpPair[pairs.Count()]; int cnt = 0; if (UseParallel) { Parallel.For(0, pairs.Count, (i) => { ret[i] = process(pairs[i]); float progress = 0.22f+0.11f * (cnt / (float)pairs.Count); cnt++; DisplayProgressAction?.Invoke(progress); // DisplayProgress(progress); }); } else { for (int i = 0; i < pairs.Count; i++) { var item = pairs[i]; ret[i] = process(item); float progress = 0.22f + 0.11f * (cnt / (float)pairs.Count); cnt++; DisplayProgress(progress); } } return ret.ToArray(); } public NfpPair process(NfpPair pair) { var A = rotatePolygon(pair.A, pair.ARotation); var B = rotatePolygon(pair.B, pair.BRotation); /////////////////// var Ac = _Clipper.ScaleUpPaths(A, 10000000); var Bc = _Clipper.ScaleUpPaths(B, 10000000); for (var i = 0; i < Bc.Length; i++) { Bc[i].X *= -1; Bc[i].Y *= -1; } var solution = ClipperLib.Clipper.MinkowskiSum(new List(Ac), new List(Bc), true); NFP clipperNfp = null; double? largestArea = null; for (int i = 0; i < solution.Count(); i++) { var n = toNestCoordinates(solution[i].ToArray(), 10000000); var sarea = -GeometryUtil.polygonArea(n); if (largestArea == null || largestArea < sarea) { clipperNfp = n; largestArea = sarea; } } for (var i = 0; i < clipperNfp.length; i++) { clipperNfp[i].x += B[0].x; clipperNfp[i].y += B[0].y; } //return new SvgNestPort.NFP[] { new SvgNestPort.NFP() { Points = clipperNfp.Points } }; ////////////// pair.A = null; pair.B = null; pair.nfp = clipperNfp; return pair; } public static NFP toNestCoordinates(IntPoint[] polygon, double scale) { //var clone = new List(); NFP nfpReturn = new NFP(); for (var i = 0; i < polygon.Count(); i++) { nfpReturn.AddPoint(new SvgPoint( polygon[i].X / scale, polygon[i].Y / scale )); } // return new NFP() { Points = clone.ToArray() }; return nfpReturn; } public static NFP getHull(NFP polygon) { // convert to hulljs format /*var hull = new ConvexHullGrahamScan(); for(var i=0; i clipperNfp = new List(); // children first if (nfp.children != null && nfp.children.Count > 0) { for (var j = 0; j < nfp.children.Count; j++) { if (GeometryUtil.polygonArea(nfp.children[j]) < 0) { nfp.children[j].reverse(); } //var childNfp = SvgNest.toClipperCoordinates(nfp.children[j]); var childNfp = _Clipper.ScaleUpPaths(nfp.children[j], clipperScale); clipperNfp.Add(childNfp); } } if (GeometryUtil.polygonArea(nfp) > 0) { nfp.reverse(); } //var outerNfp = SvgNest.toClipperCoordinates(nfp); // clipper js defines holes based on orientation var outerNfp = _Clipper.ScaleUpPaths(nfp, clipperScale); //var cleaned = ClipperLib.Clipper.CleanPolygon(outerNfp, 0.00001*config.clipperScale); clipperNfp.Add(outerNfp); //var area = Math.abs(ClipperLib.Clipper.Area(cleaned)); return clipperNfp.ToArray(); } // inner nfps can be an array of nfps, outer nfps are always singular public static IntPoint[][] innerNfpToClipperCoordinates(NFP[] nfp, double clipperScale) { List clipperNfp = new List(); foreach (NFP item in nfp) { var clip = nfpToClipperCoordinates(item, clipperScale); clipperNfp.AddRange(clip); } return clipperNfp.ToArray(); } public NFP[] NewMinkowskiSum(NFP pattern, NFP path, int type, bool useChilds = false, bool takeOnlyBiggestArea = true) { var key = pattern.source + ";" + path.source + ";" + pattern.rotation + ";" + path.rotation; bool cacheAllow = type != 1; if (cacheProcess.ContainsKey(key) && cacheAllow) { return cacheProcess[key]; } var ac = _Clipper.ScaleUpPaths(pattern, 10000000); List> solution = null; if (useChilds) { var bc = Background.nfpToClipperCoordinates(path, 10000000); for (var i = 0; i < bc.Length; i++) { for (int j = 0; j < bc[i].Length; j++) { bc[i][j].X *= -1; bc[i][j].Y *= -1; } } solution = ClipperLib.Clipper.MinkowskiSum(new List(ac), new List>(bc.Select(z => z.ToList())), true); } else { var bc = _Clipper.ScaleUpPaths(path, 10000000); for (var i = 0; i < bc.Length; i++) { bc[i].X *= -1; bc[i].Y *= -1; } solution = Clipper.MinkowskiSum(new List(ac), new List(bc), true); } NFP clipperNfp = null; double? largestArea = null; int largestIndex = -1; for (int i = 0; i < solution.Count; i++) { var n = toNestCoordinates(solution[i].ToArray(), 10000000); var sarea = Math.Abs(GeometryUtil.polygonArea(n)); if (largestArea == null || largestArea < sarea) { clipperNfp = n; largestArea = sarea; largestIndex = i; } } if (!takeOnlyBiggestArea) { for (int j = 0; j < solution.Count; j++) { if (j == largestIndex) continue; var n = toNestCoordinates(solution[j].ToArray(), 10000000); if (clipperNfp.children == null) clipperNfp.children = new List(); clipperNfp.children.Add(n); } } for (var i = 0; i < clipperNfp.Length; i++) { clipperNfp[i].x *= -1; clipperNfp[i].y *= -1; clipperNfp[i].x += pattern[0].x; clipperNfp[i].y += pattern[0].y; } if (clipperNfp.children != null) foreach (var nFP in clipperNfp.children) { for (int j = 0; j < nFP.Length; j++) { nFP.Points[j].x *= -1; nFP.Points[j].y *= -1; nFP.Points[j].x += pattern[0].x; nFP.Points[j].y += pattern[0].y; } } var res = new[] { clipperNfp }; if (cacheAllow) { cacheProcess.Add(key, res); } return res; } public static void ExecuteSTA(Action act) { if (!Debugger.IsAttached) return; Thread thread = new Thread(() => { act(); }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); } public static bool UseExternalDll = false; public NFP getOuterNfp(NFP A, NFP B, int type, bool inside = false)//todo:?inside def? { NFP[] nfp = null; var key = new DbCacheKey() { A = A.source, B = B.source, ARotation = A.rotation, BRotation = B.rotation, //Type = type }; var doc = window.db.find(key); if (doc != null) { return doc.First(); } /* // try the file cache if the calculation will take a long time var doc = window.db.find({ A: A.source, B: B.source, Arotation: A.rotation, Brotation: B.rotation }); if (doc) { return doc; }*/ // not found in cache if (inside || (A.children != null && A.children.Count > 0)) { lock (lockobj) { if (UseExternalDll) { nfp = Process2(A, B, type); } else { nfp = NewMinkowskiSum(B, A, type, true, takeOnlyBiggestArea: false); } } } else { var Ac = _Clipper.ScaleUpPaths(A, 10000000); var Bc = _Clipper.ScaleUpPaths(B, 10000000); for (var i = 0; i < Bc.Length; i++) { Bc[i].X *= -1; Bc[i].Y *= -1; } //Stopwatch sw = new Stopwatch(); //sw.Start(); var solution = ClipperLib.Clipper.MinkowskiSum(new List(Ac), new List(Bc), true); //sw.Stop(); //Trace.WriteLine($"ClipperLib.Clipper.MinkowskiSum: {sw.ElapsedMilliseconds}"); NFP clipperNfp = null; double? largestArea = null; for (int i = 0; i < solution.Count(); i++) { var n = Background.toNestCoordinates(solution[i].ToArray(), 10000000); var sarea = GeometryUtil.polygonArea(n); if (largestArea == null || largestArea > sarea) { clipperNfp = n; largestArea = sarea; } } for (var i = 0; i < clipperNfp.length; i++) { clipperNfp[i].x += B[0].x; clipperNfp[i].y += B[0].y; } nfp = new NFP[] { new NFP() { Points = clipperNfp.Points } }; } if (nfp == null || nfp.Length == 0) { //console.log('holy shit', nfp, A, B, JSON.stringify(A), JSON.stringify(B)); return null; } NFP nfps = nfp.First(); /* nfp = nfp.pop(); */ if (nfps == null || nfps.Length == 0) { return null; } /* if (!nfp || nfp.length == 0) { return null; } */ if (!inside && A.source != null && B.source != null) { var doc2 = new DbCacheKey() { A = A.source.Value, B = B.source.Value, ARotation = A.rotation, BRotation = B.rotation, nfp = nfp }; window.db.insert(doc2); } /* if (!inside && typeof A.source !== 'undefined' && typeof B.source !== 'undefined') { // insert into db doc = { A: A.source, B: B.source, Arotation: A.rotation, Brotation: B.rotation, nfp: nfp }; window.db.insert(doc); } */ return nfps; } } public class ClipCacheItem { public int index; public IntPoint[][] nfpp; } public class dbCache { public dbCache(windowUnk w) { window = w; } public bool has(DbCacheKey obj) { lock (lockobj) { var key = getKey(obj); //var key = "A" + obj.A + "B" + obj.B + "Arot" + (int)Math.Round(obj.ARotation) + "Brot" + (int)Math.Round(obj.BRotation); if (window.nfpCache.ContainsKey(key)) { return true; } return false; } } public windowUnk window; public object lockobj = new object(); string getKey(DbCacheKey obj) { // var key = "A" + obj.A + "B" + obj.B + "Arot" + (int)Math.Round(obj.ARotation * 10000) + "Brot" + (int)Math.Round((obj.BRotation * 10000)) + ";" + obj.Type; return obj.KeyValue; } internal void insert(DbCacheKey obj, bool inner = false) { var key = getKey(obj); //if (window.performance.memory.totalJSHeapSize < 0.8 * window.performance.memory.jsHeapSizeLimit) { lock (lockobj) { if (!window.nfpCache.ContainsKey(key)) { window.nfpCache.Add(key, Background.cloneNfp(obj.nfp, inner).ToList()); } else { throw new Exception("trouble .cache allready has such key"); // var lst = Background.cloneNfp(obj.nfp, inner).ToList(); // window.nfpCache[key] = lst; } } //console.log('cached: ',window.cache[key].poly); //console.log('using', window.performance.memory.totalJSHeapSize/window.performance.memory.jsHeapSizeLimit); } } public NFP[] find(DbCacheKey obj, bool inner = false) { lock (lockobj) { var key = getKey(obj); //var key = "A" + obj.A + "B" + obj.B + "Arot" + (int)Math.Round(obj.ARotation) + "Brot" + (int)Math.Round((obj.BRotation)); //console.log('key: ', key); if (window.nfpCache.ContainsKey(key)) { return Background.cloneNfp(window.nfpCache[key].ToArray(), inner); } return null; } } } public class windowUnk { public windowUnk() { db = new dbCache(this); } public Dictionary> nfpCache = new Dictionary>(); public dbCache db; } }