/** * 将NestPath列表转换成父子关系的树 * @param list * @param idstart * @return */ public static int toTree(List <NestPath> list, int idstart) { List <NestPath> parents = new List <NestPath>(); int id = idstart; /** * 找出所有的内回环 */ for (int i = 0; i < list.Count; i++) { NestPath p = list[i]; bool isChild = false; for (int j = 0; j < list.Count; j++) { if (j == i) { continue; } if (GeometryUtil.pointInPolygon(p.getSegments()[0], list[j]) == true) { list[j].getChildren().Add(p); p.setParent(list[j]); isChild = true; break; } } if (!isChild) { parents.Add(p); } } /** * 将内环从list列表中去除 */ for (int i = 0; i < list.Count; i++) { if (parents.IndexOf(list[i]) < 0) { list.RemoveAt(i); i--; } } for (int i = 0; i < parents.Count; i++) { parents[i].setId(id); id++; } for (int i = 0; i < parents.Count; i++) { if (parents[i].getChildren().Count > 0) { id = toTree(parents[i].getChildren(), id); } } return(id); }
/** * 为一个polygon 返回一个角度 * @param part * @return */ private double randomAngle(NestPath part) { List <double> angleList = new List <double>(); double rotate = Math.Max(1, part.getRotation()); if (rotate == 0) { angleList.Add(0); } else { for (int i = 0; i < rotate; i++) { angleList.Add((360 / rotate) * i); } } //打乱角度列表的排序 angleList.Shuffle(); //Collections.shuffle(angleList); for (int i = 0; i < angleList.Count; i++) { Bound rotatedPart = GeometryUtil.rotatePolygon(part, angleList[i]); if (rotatedPart.getWidth() < binBounds.getWidth() && rotatedPart.getHeight() < binBounds.getHeight()) { return(angleList[i]); } } /** * 没有找到合法的角度 */ return(-1); }
/** * 在遗传算法中每次突变或者是交配产生出新的种群时,可能会出现板件与旋转角度不适配的结果,需要重新检查并适配。 * @param binPolygon * @param tree * @return */ private static List <int> checkIfCanBePlaced(NestPath binPolygon, List <NestPath> tree) { List <int> CanBePlacdPolygonIndex = new List <int>(); Bound binBound = GeometryUtil.getPolygonBounds(binPolygon); for (int i = 0; i < tree.Count; i++) { NestPath nestPath = tree[i]; if (nestPath.getRotation() == 0) { Bound bound = GeometryUtil.getPolygonBounds(nestPath); if (bound.width < binBound.width && bound.height < binBound.height) { CanBePlacdPolygonIndex.Add(i); continue; } } else { for (int j = 0; j < nestPath.getRotation(); j++) { Bound rotatedBound = GeometryUtil.rotatePolygon(nestPath, (360 / nestPath.getRotation()) * j); if (rotatedBound.width < binBound.width && rotatedBound.height < binBound.height) { CanBePlacdPolygonIndex.Add(i); break; } } } } return(CanBePlacdPolygonIndex); }
static void Main(string[] args) { NestPath bin = new NestPath(); double binWidth = 75; double binHeight = 41; bin.add(0, 0); bin.add(binWidth, 0); bin.add(binWidth, binHeight); bin.add(0, binHeight); Console.WriteLine("Bin Size : Width = " + binWidth + " Height=" + binHeight); //将多边形转换为坐标形式 var nestPaths = SvgUtil.transferSvgIntoPolygons("test3.xml"); Console.WriteLine("Reading File = test1.xml"); Console.WriteLine("No of parts = " + nestPaths.Count); Config config = new Config(); Console.WriteLine("Configuring Nest"); Nest nest = new Nest(bin, nestPaths, config, 2); Console.WriteLine("Performing Nest"); List <List <Placement> > appliedPlacement = nest.startNest(); Console.WriteLine("Nesting Completed"); var svgPolygons = SvgUtil.svgGenerator(nestPaths, appliedPlacement, binWidth, binHeight); Console.WriteLine("Converted to SVG format"); SvgUtil.saveSvgFile(svgPolygons, "output.svg"); Console.WriteLine("Saved svg file..Opening File"); Process.Start("output.svg"); Console.ReadLine(); }
/** * 创建一个新的Nest对象 * @param binPath 底板多边形 * @param parts 板件多边形列表 * @param config 参数设置 * @param count 迭代计算次数 */ public Nest(NestPath binPath, List <NestPath> parts, Config config, int count) { this.binPath = binPath; this.parts = parts; this.config = config; this.loopCount = count; nfpCache = new Dictionary <string, List <NestPath> >(); }
public GeneticAlgorithm(List <NestPath> adam, NestPath bin, Config config) { this.adam = adam; this.bin = bin; this.config = config; this.binBounds = GeometryUtil.getPolygonBounds(bin); population = new List <Individual>(); init(); }
public static NestPath toNestCoordinates(Path polygon) { NestPath clone = new NestPath(); for (int i = 0; i < polygon.Count; i++) { Segment s = new Segment((double)polygon[i].X / Config.CLIIPER_SCALE, (double)polygon[i].Y / Config.CLIIPER_SCALE); clone.add(s); } return(clone); }
/** * 坐标转换,与clipper库交互必须坐标转换 * @param polygon * @return */ public static Path scaleUp2ClipperCoordinates(NestPath polygon) { Path p = new Path(); foreach (Segment s in polygon.getSegments()) { ClipperCoor cc = CommonUtil.toClipperCoor(s.x, s.y); p.Add(new IntPoint(cc.getX(), cc.getY())); } return(p); }
public static NestPath clipperToNestPath(Path polygon) { NestPath normal = new NestPath(); for (int i = 0; i < polygon.Count; i++) { NestCoor nestCoor = toNestCoor(polygon[i].X, polygon[i].Y); normal.add(new Segment(nestCoor.getX(), nestCoor.getY())); } return(normal); }
public static NestPath Path2NestPath(Path path) { NestPath nestPath = new NestPath(); for (int i = 0; i < path.Count; i++) { IntPoint lp = path[i]; NestCoor coor = CommonUtil.toNestCoor(lp.X, lp.Y); nestPath.add(new Segment(coor.getX(), coor.getY())); } return(nestPath); }
public static Path NestPath2Path(NestPath nestPath) { Path path = new Path(); foreach (Segment s in nestPath.getSegments()) { ClipperCoor coor = CommonUtil.toClipperCoor(s.getX(), s.getY()); var lp = new IntPoint(coor.getX(), coor.getY()); path.Add(lp); } return(path); }
public static List <NestPath> polygonOffset(NestPath polygon, double offset) { List <NestPath> result = new List <NestPath>(); if (offset == 0 || GeometryUtil.almostEqual(offset, 0)) { /** * return EmptyResult */ return(result); } Path p = new Path(); foreach (Segment s in polygon.getSegments()) { ClipperCoor cc = toClipperCoor(s.getX(), s.getY()); p.Add(new IntPoint(cc.getX(), cc.getY())); } int miterLimit = 2; ClipperOffset co = new ClipperOffset(miterLimit, Config.CURVE_TOLERANCE * Config.CLIIPER_SCALE); co.AddPath(p, JoinType.jtRound, EndType.etClosedPolygon); Paths newpaths = new Paths(); co.Execute(ref newpaths, offset * Config.CLIIPER_SCALE); /** * 这里的length是1的话就是我们想要的 */ for (int i = 0; i < newpaths.Count; i++) { result.Add(CommonUtil.clipperToNestPath(newpaths[i])); } if (offset > 0) { NestPath from = result[0]; if (GeometryUtil.polygonArea(from) > 0) { from.reverse(); } from.add(from.get(0)); from.getSegments().RemoveAt(0); } return(result); }
public static List <NestPath> transferSvgIntoPolygons(string xmlFilePath) { List <NestPath> nestPaths = new List <NestPath>(); XDocument document = XDocument.Load(xmlFilePath); List <XElement> elementList = document.Root.DescendantNodes().OfType <XElement>().ToList(); int count = 0; foreach (XElement element in elementList) { count++; if ("polygon" == (element.Name)) { String datalist = element.Attributes((XName)"points").ToList()[0].Value.ToString(); NestPath polygon = new NestPath(); foreach (String s in datalist.Split(' ')) { var temp = s.Trim(); if (temp.IndexOf(",") == -1) { continue; } String[] value = s.Split(','); double x = Double.Parse(value[0]); double y = Double.Parse(value[1]); polygon.add(x, y); } polygon.bid = count; polygon.setRotation(4); nestPaths.Add(polygon); } else if ("rect" == element.Name) { double width = Double.Parse(element.Attributes((XName)"width").ToList()[0].Value.ToString()); double height = Double.Parse(element.Attributes((XName)"height").ToList()[0].Value.ToString()); double x = Double.Parse(element.Attributes((XName)"x").ToList()[0].Value.ToString()); double y = Double.Parse(element.Attributes((XName)"y").ToList()[0].Value.ToString()); NestPath rect = new NestPath(); rect.add(x, y); rect.add(x + width, y); rect.add(x + width, y + height); rect.add(x, y + height); rect.bid = count; rect.setRotation(4); nestPaths.Add(rect); } } return(nestPaths); }
public Individual(Individual individual) { fitness = individual.fitness; placement = new List <NestPath>(); rotation = new List <double>(); for (int i = 0; i < individual.placement.Count; i++) { NestPath cloneNestPath = new NestPath(individual.placement[i]); placement.Add(cloneNestPath); } for (int i = 0; i < individual.rotation.Count; i++) { double rotationAngle = individual.getRotation()[i]; rotation.Add(rotationAngle); } }
public static List <String> svgGenerator(List <NestPath> list, List <List <Placement> > applied, double binwidth, double binHeight) { List <String> strings = new List <String>(); int x = 0; //代表离y轴初始边的距离 int y = 0; //代表离轴初始边的距离 foreach (List <Placement> binlist in applied) { String s = " <g transform=\"translate(" + x + " " + y + ")\">" + "\n"; s += " <rect x=\"0\" y=\"0\" width=\"" + binwidth + "\" height=\"" + binHeight + "\" fill=\"none\" stroke=\"#010101\" stroke-width=\"1\" />\n"; foreach (Placement placement in binlist) { int bid = placement.bid; NestPath nestPath = getNestPathByBid(bid, list); double ox = placement.translate.x; double oy = placement.translate.y; double rotate = placement.rotate; var translateX = ox + x; var translateY = oy + y; s += "<g transform=\"translate(" + translateX + " " + translateY + ") rotate(" + rotate + ")\"> \n"; //s += "<path d=\""; //for (int i = 0; i < nestPath.getSegments().Count; i++) //{ // if (i == 0) // { // s += "M"; // } // else // { // s += "L"; // } // Segment segment = nestPath.get(i); // s += segment.x + " " + segment.y + " "; //} //s += "Z\" fill=\"#8498d1\" stroke=\"#010101\" stroke-width=\"1\" />" + " \n"; s += nestPath.getElement(); s += "</g> \n"; } s += "</g> \n"; y += (int)(binHeight + 0); strings.Add(s); } return(strings); }
public void checkAndUpdate(Individual individual) { for (int i = 0; i < individual.placement.Count; i++) { double angle = individual.getRotation()[i]; NestPath nestPath = individual.getPlacement()[i]; Bound rotateBound = GeometryUtil.rotatePolygon(nestPath, angle); if (rotateBound.width < binBounds.width && rotateBound.height < binBounds.height) { continue; } else { double safeAngle = randomAngle(nestPath); individual.getRotation()[i] = safeAngle; } } }
/** * 对应于JS项目中的getParts */ public static List <NestPath> BuildTree(List <NestPath> parts, double curve_tolerance) { List <NestPath> polygons = new List <NestPath>(); for (int i = 0; i < parts.Count; i++) { NestPath cleanPoly = NestPath.cleanNestPath(parts[i]); cleanPoly.bid = parts[i].bid; if (cleanPoly.size() > 2 && Math.Abs(GeometryUtil.polygonArea(cleanPoly)) > curve_tolerance * curve_tolerance) { cleanPoly.setSource(i); polygons.Add(cleanPoly); } } CommonUtil.toTree(polygons, 0); return(polygons); }
//图像偏置算法 public static void offsetTree(List <NestPath> t, double offset) { for (int i = 0; i < t.Count; i++) { List <NestPath> offsetPaths = polygonOffset(t[i], offset); if (offsetPaths.Count == 1) { t[i].clear(); NestPath from = offsetPaths[0]; foreach (Segment s in from.getSegments()) { t[i].add(s); } } if (t[i].getChildren().Count > 0) { offsetTree(t[i].getChildren(), -offset); } } }
/** * 通过id与bid将translate和rotate绑定到对应板件上 * @param best * @param tree * @return */ public static List <List <Placement> > applyPlacement(Result best, List <NestPath> tree) { List <List <Placement> > applyPlacement = new List <List <Placement> >(); for (int i = 0; i < best.placements.Count; i++) { List <Placement> binTranslate = new List <Placement>(); for (int j = 0; j < best.placements[i].Count; j++) { Vector v = best.placements[i][j]; NestPath nestPath = tree[v.id]; foreach (NestPath child in nestPath.getChildren()) { Placement chPlacement = new Placement(child.bid, new Segment(v.x, v.y), v.rotation); binTranslate.Add(chPlacement); } Placement placement = new Placement(nestPath.bid, new Segment(v.x, v.y), v.rotation); binTranslate.Add(placement); } applyPlacement.Add(binTranslate); } return(applyPlacement); }
public void add(NestPath np) { parts.Add(np); }
//private static Gson gson = new GsonBuilder().create(); /** * * @param binPolygon 底板参数 * @param config 设置 * @param nfpCache nfp列表 */ public PlacementWorker(NestPath binPolygon, Config config, Dictionary <String, List <NestPath> > nfpCache) { this.binPolygon = binPolygon; this.config = config; this.nfpCache = nfpCache; }
public static List <NestPath> transferSvgIntoPolygons(string xmlFilePath) { List <NestPath> nestPaths = new List <NestPath>(); XDocument document = XDocument.Load(xmlFilePath); List <XElement> elementList = document.Root.DescendantNodes().OfType <XElement>().ToList(); //对于测试库的数据,需要做一下筛选,如果是自己弄得测试数据,这句可以不要 var elements = elementList.Where(p => p.Name == "polygon"); int count = 0; int index = 0; foreach (XElement element in elements) { count++; //对于测试库的数据要加上这一句 var elementFirstNode = (XElement)element.FirstNode; var rotation = int.Parse(element.Attributes((XName)"nVertices").ToList()[0].Value.ToString()); switch (elementFirstNode.Name.ToString()) { case "polyline": case "polygon": { String datalist = element.Attributes((XName)"points").ToList()[0].Value.ToString(); NestPath polygon = new NestPath(); polygon.setElement(element.ToString()); foreach (String s in datalist.Split(' ')) { var temp = s.Trim(); if (temp.IndexOf(",") == -1) { continue; } String[] value = s.Split(','); double x = Double.Parse(value[0]); double y = Double.Parse(value[1]); polygon.add(x, y); //点的坐标 } polygon.bid = count; //多边形的序号 polygon.setRotation(4); //旋转角度,值设为4时代表角度可以旋转90、180、270,该值一般为360的倍数 nestPaths.Add(polygon); break; } case "lines": { index++; var piesCount = int.Parse(elementFirstNode.Attributes((XName)"count").ToList()[0].Value.ToString()); var dataList = elementFirstNode.DescendantNodes().OfType <XElement>().ToList(); NestPath polygon = new NestPath(); string point = null; foreach (var data in dataList) { if (data.Name == "segment") { //为了保证在排样前各个图形的坐标不重合,所以后面增加2000 * index var x0 = Double.Parse(data.Attributes((XName)"x0").ToList()[0].Value.ToString()) + 20 * index; var y0 = Double.Parse(data.Attributes((XName)"y0").ToList()[0].Value.ToString()) + 20 * index; var x1 = Double.Parse(data.Attributes((XName)"x1").ToList()[0].Value.ToString()) + 20 * index; var y1 = Double.Parse(data.Attributes((XName)"y1").ToList()[0].Value.ToString()) + 20 * index; point += x0 + "," + y0 + " "; polygon.add(x0, y0); polygon.add(x1, y1); } } var listTemp = polygon.getSegments(); var newList = new List <Segment>(); for (int i = 0; i < listTemp.Count; i++) { if (i % 2 == 0) { newList.Add(listTemp[i]); } } polygon.setSegments(newList); polygon.bid = count; //多边形的序号 polygon.setRotation(rotation); //旋转角度,值设为4时代表角度可以旋转90、180、270,该值一般为360的倍数 var elementTemp = " <polygon fill=\"none\" stroke=\"#010101\" stroke-miterlimit=\"10\" points= \"" + point + " \"></polygon>"; for (int i = 0; i < piesCount; i++) { polygon.setElement(elementTemp); nestPaths.Add(polygon); } break; } case "rect": { double width = Double.Parse(element.Attributes((XName)"width").ToList()[0].Value.ToString()); double height = Double.Parse(element.Attributes((XName)"height").ToList()[0].Value.ToString()); double x = Double.Parse(element.Attributes((XName)"x").ToList()[0].Value.ToString()); double y = Double.Parse(element.Attributes((XName)"y").ToList()[0].Value.ToString()); NestPath rect = new NestPath(); rect.setElement(element.ToString()); rect.add(x, y); rect.add(x + width, y); rect.add(x + width, y + height); rect.add(x, y + height); rect.bid = count; rect.setRotation(4); nestPaths.Add(rect); break; } case "circle": //对于圆形,给转换成坐标形式 { double cx = Double.Parse(element.Attributes((XName)"cx").ToList()[0].Value.ToString()); double cy = Double.Parse(element.Attributes((XName)"cy").ToList()[0].Value.ToString()); double radius = Double.Parse(element.Attributes((XName)"r").ToList()[0].Value.ToString()); // num is the smallest number of segments required to approximate the circle to the given tolerance var num = Math.Ceiling((2 * Math.PI) / Math.Acos(1 - (ToleranceConfig.tolerance / radius))); if (num < 3) { num = 3; } NestPath circle = new NestPath(); circle.setElement(element.ToString()); circle.bid = count; circle.setRotation(4); for (var i = 0; i < num; i++) { var theta = i * ((2 * Math.PI) / num); double x = radius * Math.Cos(theta) + cx; double y = radius * Math.Sin(theta) + cy; circle.add(x, y); } nestPaths.Add(circle); break; } case "ellipse": //对于椭圆,给转换成坐标形式 { // same as circle case. There is probably a way to reduce points but for convenience we will just flatten the equivalent circular polygon var rx = Double.Parse(element.Attributes((XName)"rx").ToList()[0].Value.ToString()); var ry = Double.Parse(element.Attributes((XName)"ry").ToList()[0].Value.ToString()); var maxradius = Math.Max(rx, ry); var cx = Double.Parse(element.Attributes((XName)"cx").ToList()[0].Value.ToString()); var cy = Double.Parse(element.Attributes((XName)"cy").ToList()[0].Value.ToString()); var num = Math.Ceiling((2 * Math.PI) / Math.Acos(1 - (ToleranceConfig.tolerance / maxradius))); if (num < 3) { num = 3; } NestPath ellipse = new NestPath(); ellipse.setElement(element.ToString()); ellipse.bid = count; ellipse.setRotation(4); for (var i = 0; i < num; i++) { var theta = i * ((2 * Math.PI) / num); double x = maxradius * Math.Cos(theta) + cx; double y = maxradius * Math.Sin(theta) + cy; ellipse.add(x, y); } nestPaths.Add(ellipse); break; } case "path": //对于带弧形的多边形,给转换成坐标形式 { var path = element.Attributes((XName)"d").ToList()[0].Value.ToString(); var pathNumbers = transferPathToNumber(path); string pathNumberType = "MLHVCSQTA"; NestPath pathPloy = new NestPath(); pathPloy.setElement(element.ToString()); pathPloy.bid = count; pathPloy.setRotation(4); double x, y, x0, y0, x1, y1, x2, y2, prevx, prevy, prevx1, prevy1, prevx2, prevy2; x = y = x0 = y0 = x1 = y1 = x2 = y2 = prevx = prevy = prevx1 = prevy1 = prevx2 = prevy2 = 0; for (var i = 0; i < pathNumbers.Count; i++) { var s = pathNumbers[i].Numbers; ////对应C#中的pathNumber的Numbers var command = pathNumbers[i].Type; //对应C#中的pathNumber的path type prevx = x; prevy = y; prevx1 = x1; prevy1 = y1; prevx2 = x2; prevy2 = y2; if (pathNumberType.Contains(command)) { switch (command) { case "M": case "L": case "T": { x = s[0]; y = s[1]; break; } case "H": { x = s[0]; break; } case "V": { y = s[0]; break; } case "Q": { x1 = s[0]; y1 = s[1]; x = s[2]; y = s[3]; break; } case "S": { x2 = s[0]; y2 = s[1]; x = s[2]; y = s[3]; break; } case "C": { x1 = s[0]; y1 = s[1]; x2 = s[2]; y2 = s[3]; x = s[4]; y = s[5]; break; } } } else { switch (command) { case "m": case "l": case "t": { x += s[0]; y += s[1]; break; } case "h": { x += s[0]; break; } case "v": { y += s[0]; break; } case "q": { x1 = x + s[0]; y1 = y + s[1]; x += s[2]; y += s[3]; break; } case "s": { x2 = x + s[0]; y2 = y + s[1]; x += s[2]; y += s[3]; break; } case "c": { x1 = x + s[0]; y1 = y + s[1]; x2 = x + s[2]; y2 = y + s[3]; x += s[4]; y += s[5]; break; } } } switch (command) { // linear line types case "m": case "M": case "l": case "L": case "h": case "H": case "v": case "V": pathPloy.add(x, y); break; // Quadratic Beziers case "t": case "T": { // implicit control point var tPathNumberType = "QqTt"; if (i > 0 && tPathNumberType.Contains(pathNumbers[i - 1].Type)) { x1 = prevx + (prevx - prevx1); y1 = prevy + (prevy - prevy1); } else { x1 = prevx; y1 = prevy; } break; } case "q": case "Q": { var pointlist = GeometryUtil.QuadraticBezierLinearize(new Segment(x: prevx, y: prevy), new Segment(x: x, y: y), new Segment(x: x1, y: y1), ToleranceConfig.tolerance); pointlist.Remove(pointlist[0]); // firstpoint would already be in the poly for (var j = 0; j < pointlist.Count; j++) { pathPloy.add(pointlist[j].x, pointlist[j].y); } break; } case "s": case "S": { var sPathNumberType = "CcSs"; if (i > 0 && sPathNumberType.Contains(pathNumbers[i - 1].Type)) { x1 = prevx + (prevx - prevx2); y1 = prevy + (prevy - prevy2); } else { x1 = prevx; y1 = prevy; } break; } case "c": case "C": { var pointlist = GeometryUtil.CubicBezierLinearize(new Segment(x: prevx, y: prevy), new Segment(x: x, y: y), new Segment(x: x1, y: y1), new Segment(x: x2, y: y2), ToleranceConfig.tolerance); pointlist.Remove(pointlist[0]); // firstpoint would already be in the poly for (var j = 0; j < pointlist.Count; j++) { pathPloy.add(pointlist[j].x, pointlist[j].y); } break; } case "a": case "A": { //var pointlist = GeometryUtil.Arc.linearize({ x: prevx, y: prevy}, { x: x, y: y}, s.r1, s.r2, s.angle, s.largeArcFlag,s.sweepFlag, this.conf.tolerance //pointlist.shift(); //for (var j = 0; j < pointlist.length; j++) //{ // var point = { }; // point.x = pointlist[j].x; // point.y = pointlist[j].y; // poly.push(point); //} break; } case "z": case "Z": { x = x0; y = y0; break; } } // Record the start of a subpath if (command == "M" || command == "m") { x0 = x; y0 = y; } } // 判断最后一个点是不是和第一个点一样,如果一样,就去除最后一个点 while (pathPloy.getSegments().Count > 0 && GeometryUtil.almostEqual(pathPloy.getSegments()[0].x, pathPloy.getSegments()[pathPloy.getSegments().Count - 1].x, ToleranceConfig.toleranceSvg) && GeometryUtil.almostEqual(pathPloy.getSegments()[0].y, pathPloy.getSegments()[pathPloy.getSegments().Count - 1].y, ToleranceConfig.toleranceSvg)) { pathPloy.getSegments().RemoveAt(pathPloy.getSegments().Count - 1); } nestPaths.Add(pathPloy); break; } } } return(nestPaths); }
public void setBin(NestPath bin) { this.bin = bin; }
/** * binPath是作为底板的NestPath , polys则为板件的Path列表 * 这个方法是为了将binPath和polys在不改变自身形状,角度的情况下放置在一个坐标系内,保证两两之间不交叉 * @param binPath * @param polys */ public static void ChangePosition(NestPath binPath, List <NestPath> polys) { }
public void setBinPath(NestPath binPath) { this.binPath = binPath; }
/** * 一次迭代计算 * @param tree 板件列表(去掉了带孔的多边形内孔的点集) * @param binPolygon 底板 * @param config 设置 * @return */ public Result launchWorkers(List <NestPath> tree, NestPath binPolygon, Config config) { launchcount++; if (GA == null) { List <NestPath> adam = new List <NestPath>(); foreach (var nestPath in tree) { var clone = new NestPath(nestPath); adam.Add(clone); } foreach (NestPath nestPath in adam) { nestPath.area = GeometryUtil.polygonArea(nestPath); } //按零件的面积由大到小排序 adam.Sort((x, y) => x.area.CompareTo(y.area)); //Collections.sort(adam); GA = new GeneticAlgorithm(adam, binPolygon, config); } Individual individual = null; for (int i = 0; i < GA.population.Count; i++) { if (GA.population[i].getFitness() < 0) { individual = GA.population[i]; break; } } // if(individual == null ){ // GA.generation(); // individual = GA.population.get(1); // } if (launchcount > 1 && individual == null) { GA.generation(); individual = GA.population[1]; } // 以上为GA List <NestPath> placelist = individual.getPlacement(); List <double> rotations = individual.getRotation(); List <int> ids = new List <int>(); for (int i = 0; i < placelist.Count; i++) { ids.Add(placelist[i].getId()); placelist[i].setRotation(rotations[i]); } List <NfpPair> nfpPairs = new List <NfpPair>(); NfpKey key = null; /** * 如果在nfpCache里没找到nfpKey 则添加进nfpPairs */ for (int i = 0; i < placelist.Count; i++) { NestPath part = placelist[i]; //这个是零件和底板之间形成的nfp,所以inside这个参数为true key = new NfpKey(binPolygon.getId(), part.getId(), true, 0, part.getRotation()); if (!nfpCache.ContainsKey(serialize.Serialize(key))) { nfpPairs.Add(new NfpPair(binPolygon, part, key)); } else { } //这个是零件之间相互形成的nfp,所以inside这个参数为false for (int j = 0; j < i; j++) { NestPath placed = placelist[j]; NfpKey keyed = new NfpKey(placed.getId(), part.getId(), false, rotations[j], rotations[i]); nfpPairs.Add(new NfpPair(placed, part, keyed)); } } /** * 第一次nfpCache为空 ,nfpCache存的是nfpKey所对应的两个polygon所形成的Nfp( List<NestPath> ) */ List <ParallelData> generatedNfp = new List <ParallelData>(); foreach (NfpPair nfpPair in nfpPairs) { ParallelData dataTemp = NfpUtil.nfpGenerator(nfpPair, config); generatedNfp.Add(dataTemp); } for (int i = 0; i < generatedNfp.Count; i++) { ParallelData Nfp = generatedNfp[i]; //TODO remove gson & generate a new key algorithm String tkey = serialize.Serialize(Nfp.getKey()); //gson.toJson(Nfp.getKey()); if (!nfpCache.ContainsKey(tkey)) { nfpCache.Add(tkey, Nfp.value); } else { } } PlacementWorker worker = new PlacementWorker(binPolygon, config, nfpCache); List <NestPath> placeListSlice = new List <NestPath>(); for (int i = 0; i < placelist.Count; i++) { placeListSlice.Add(new NestPath(placelist[i])); } List <List <NestPath> > data = new List <List <NestPath> >(); data.Add(placeListSlice); List <Result> placements = new List <Result>(); for (int i = 0; i < data.Count; i++) { Result result = worker.placePaths(data[i]); placements.Add(result); } if (placements.Count == 0) { return(null); } individual.fitness = placements[0].fitness; Result bestResult = placements[0]; for (int i = 1; i < placements.Count; i++) { if (placements[i].fitness < bestResult.fitness) { bestResult = placements[i]; } } return(bestResult); }
/** * 开始进行Nest计算 * @return */ public List <List <Placement> > startNest() { //去除parts点中有孔的点,只对最外围的零件进行排样 List <NestPath> tree = CommonUtil.BuildTree(parts, Config.CURVE_TOLERANCE); //根据设定的零件之间的距离,将图像由内向外进行偏置 CommonUtil.offsetTree(tree, 0.5 * config.SPACING); binPath.config = config; foreach (NestPath nestPath in parts) { nestPath.config = config; } //自相交多边形的清理 NestPath binPolygon = NestPath.cleanNestPath(binPath); Bound binBound = GeometryUtil.getPolygonBounds(binPolygon); //如果零件之间设定了间距,则底板也需要进行偏置 if (config.SPACING > 0) { List <NestPath> offsetBin = CommonUtil.polygonOffset(binPolygon, -0.5 * config.SPACING); if (offsetBin.Count == 1) { binPolygon = offsetBin[0]; } } binPolygon.setId(-1);//这个是用来干嘛的?可能是为了让底板的编号特殊一些 //判断零件是否都能在底板中放置,如果零件的大小超过底板的大小,则直接清除 List <int> integers = checkIfCanBePlaced(binPolygon, tree); List <NestPath> safeTree = new List <NestPath>(); foreach (int i in integers) { safeTree.Add(tree[i]); } tree = safeTree; //计算多边形的面积。如果面积值大于零,说明多边形方向为反方向,需要进行方向转换,但是为什么要做这个操作呢? if (GeometryUtil.polygonArea(binPolygon) > 0) { binPolygon.reverse(); } /** * 确保为逆时针 */ for (int i = 0; i < tree.Count; i++) { Segment start = tree[i].get(0); Segment end = tree[i].get(tree[i].size() - 1); if (start == end || GeometryUtil.almostEqual(start.x, end.x) && GeometryUtil.almostEqual(start.y, end.y)) { tree[i].pop(); } if (GeometryUtil.polygonArea(tree[i]) > 0) { tree[i].reverse(); } } launchcount = 0; Result best = null; // Tree Modification based on nest4J //List<NestPath> modifiedTree = new List<NestPath>(); //for (int i = 0; i < tree.Count; i++) //{ // List<Segment> modifiedSegment = new List<Segment>(); // NestPath currentTree = tree[i]; // List<Segment> currentTreeSegments = currentTree.getSegments(); // modifiedSegment.Add(currentTreeSegments[currentTreeSegments.Count-1]); // for (int j = 0; j < currentTreeSegments.Count-1; j++) // { // modifiedSegment.Add(currentTreeSegments[j]); // } // currentTree.setSegments(modifiedSegment); // modifiedTree.Add(currentTree); //} //tree = modifiedTree; for (int i = 0; i < loopCount; i++) { Result result = launchWorkers(tree, binPolygon, config); if (i == 0) { best = result; } else { if (best.fitness > result.fitness) { best = result; } } } double sumarea = 0; //底板多边形的面积 double totalarea = 0; //放置所有零件的面积 //placements中的Count数据就代表了使用了几个底板的数量,如果一个底板大小不够放置所有零件,那系统会自动增加一个底板 for (int i = 0; i < best.placements.Count; i++) { totalarea += Math.Abs(GeometryUtil.polygonArea(binPolygon)); for (int j = 0; j < best.placements[i].Count; j++) { try { sumarea += Math.Abs(GeometryUtil.polygonArea(tree[best.placements[i][j].id])); } catch (Exception ex) { } } } double rate = (sumarea / totalarea) * 100; List <List <Placement> > appliedPlacement = applyPlacement(best, tree); return(appliedPlacement); }
/** * 根据板件列表与旋转角列表,通过nfp,计算板件在底板上的位置,并返回这个种群的fitness * @param paths * @return */ public Result placePaths(List <NestPath> paths) { List <NestPath> rotated = new List <NestPath>(); for (int i = 0; i < paths.Count; i++) { NestPath r = GeometryUtil.rotatePolygon2Polygon(paths[i], paths[i].getRotation()); r.setRotation(paths[i].getRotation()); r.setSource(paths[i].getSource()); r.setId(paths[i].getId()); rotated.Add(r); } paths = rotated; List <List <Vector> > allplacements = new List <List <Vector> >(); double fitness = 0; double binarea = Math.Abs(GeometryUtil.polygonArea(this.binPolygon)); String key = null; List <NestPath> nfp = null; while (paths.Count > 0) { List <NestPath> placed = new List <NestPath>(); List <Vector> placements = new List <Vector>(); fitness += 1; double minwidth = Double.MaxValue; for (int i = 0; i < paths.Count; i++) { NestPath path = paths[i]; //inner NFP key = new JavaScriptSerializer().Serialize(new NfpKey(-1, path.getId(), true, 0, path.getRotation())); //key = gson.toJson(new NfpKey(-1, path.getId(), true, 0, path.getRotation())); if (!nfpCache.ContainsKey(key)) { continue; } List <NestPath> binNfp = nfpCache[key]; // ensure exists bool error = false; for (int j = 0; j < placed.Count; j++) { key = new JavaScriptSerializer().Serialize(new NfpKey(placed[j].getId(), path.getId(), false, placed[j].getRotation(), path.getRotation())); // key = gson.toJson(new NfpKey(placed[j].getId(), path.getId(), false, placed[j].getRotation(), path.getRotation())); if (nfpCache.ContainsKey(key)) { nfp = nfpCache[key]; } else { error = true; break; } } if (error) { continue; } Vector position = null; if (placed.Count == 0) { // first placement , put it on the lefth for (int j = 0; j < binNfp.Count; j++) { for (int k = 0; k < binNfp[j].size(); k++) { if (position == null || binNfp[j].get(k).x - path.get(0).x < position.x) { position = new Vector( binNfp[j].get(k).x - path.get(0).x, binNfp[j].get(k).y - path.get(0).y, path.getId(), path.getRotation() ); } } } placements.Add(position); placed.Add(path); continue; } Paths clipperBinNfp = new Paths(); for (int j = 0; j < binNfp.Count; j++) { NestPath binNfpj = binNfp[j]; clipperBinNfp.Add(scaleUp2ClipperCoordinates(binNfpj)); } Clipper clipper = new Clipper(); Paths combinedNfp = new Paths(); for (int j = 0; j < placed.Count; j++) { key = new JavaScriptSerializer().Serialize(new NfpKey(placed[j].getId(), path.getId(), false, placed[j].getRotation(), path.getRotation())); //key = gson.toJson(new NfpKey(placed[j].getId(), path.getId(), false, placed[j].getRotation(), path.getRotation())); nfp = nfpCache[key]; if (nfp == null) { continue; } for (int k = 0; k < nfp.Count; k++) { Path clone = PlacementWorker.scaleUp2ClipperCoordinates(nfp[k]); for (int m = 0; m < clone.Count; m++) { long clx = (long)clone[m].X; long cly = (long)clone[m].Y; IntPoint intPoint = clone[m]; intPoint.X = (clx + (long)(placements[j].x * Config.CLIIPER_SCALE)); intPoint.Y = (cly + (long)(placements[j].y * Config.CLIIPER_SCALE)); clone[m] = intPoint; } //clone = clone.Cleaned(0.0001 * Config.CLIIPER_SCALE); clone = Clipper.CleanPolygon(clone, 0.0001 * Config.CLIIPER_SCALE); double areaPoly = Math.Abs(Clipper.Area(clone)); if (clone.Count > 2 && areaPoly > 0.1 * Config.CLIIPER_SCALE * Config.CLIIPER_SCALE) { clipper.AddPath(clone, PolyType.ptSubject, true); } } } if (!clipper.Execute(ClipType.ctUnion, combinedNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero)) { continue; } //difference with bin polygon Paths finalNfp = new Paths(); clipper = new Clipper(); clipper.AddPaths(combinedNfp, PolyType.ptClip, true); clipper.AddPaths(clipperBinNfp, PolyType.ptSubject, true); if (!clipper.Execute(ClipType.ctDifference, finalNfp, PolyFillType.pftNonZero, PolyFillType.pftNonZero)) { continue; } // finalNfp = finalNfp.Cleaned(0.0001 * Config.CLIIPER_SCALE); finalNfp = Clipper.CleanPolygons(finalNfp, 0.0001 * Config.CLIIPER_SCALE); for (int j = 0; j < finalNfp.Count(); j++) { //double areaPoly = Math.Abs(finalNfp[j].Area); double areaPoly = Math.Abs(Clipper.Area(finalNfp[j])); if (finalNfp[j].Count < 3 || areaPoly < 0.1 * Config.CLIIPER_SCALE * Config.CLIIPER_SCALE) { finalNfp.RemoveAt(j); j--; } } if (finalNfp == null || finalNfp.Count == 0) { continue; } List <NestPath> f = new List <NestPath>(); for (int j = 0; j < finalNfp.Count; j++) { f.Add(toNestCoordinates(finalNfp[j])); } List <NestPath> finalNfpf = f; double minarea = Double.MinValue; double minX = Double.MaxValue; NestPath nf = null; double area = Double.MinValue; Vector shifvector = null; for (int j = 0; j < finalNfpf.Count; j++) { nf = finalNfpf[j]; if (Math.Abs(GeometryUtil.polygonArea(nf)) < 2) { continue; } for (int k = 0; k < nf.size(); k++) { NestPath allpoints = new NestPath(); for (int m = 0; m < placed.Count; m++) { for (int n = 0; n < placed[m].size(); n++) { allpoints.add(new Segment(placed[m].get(n).x + placements[m].x, placed[m].get(n).y + placements[m].y)); } } shifvector = new Vector(nf.get(k).x - path.get(0).x, nf.get(k).y - path.get(0).y, path.getId(), path.getRotation(), combinedNfp); for (int m = 0; m < path.size(); m++) { allpoints.add(new Segment(path.get(m).x + shifvector.x, path.get(m).y + shifvector.y)); } Bound rectBounds = GeometryUtil.getPolygonBounds(allpoints); area = rectBounds.getWidth() * 2 + rectBounds.getHeight(); if (minarea == Double.MinValue || area < minarea || (GeometryUtil.almostEqual(minarea, area) && (minX == Double.MinValue || shifvector.x < minX))) { minarea = area; minwidth = rectBounds.getWidth(); position = shifvector; minX = shifvector.x; } } } if (position != null) { placed.Add(path); placements.Add(position); } } if (minwidth != Double.MinValue) { fitness += minwidth / binarea; } for (int i = 0; i < placed.Count; i++) { int index = paths.IndexOf(placed[i]); if (index >= 0) { paths.RemoveAt(index); } } if (placements != null && placements.Count > 0) { allplacements.Add(placements); } else { break; // something went wrong } } // there were paths that couldn't be placed fitness += 2 * paths.Count; return(new Result(allplacements, fitness, paths, binarea)); }
/** * 获取一对多边形,并生成nfp * @param pair * @param config * @return */ public static ParallelData nfpGenerator(NfpPair pair, Config config) { bool searchEdges = config.isCONCAVE(); bool useHoles = config.isUSE_HOLE(); NestPath A = GeometryUtil.rotatePolygon2Polygon(pair.getA(), pair.getKey().getArotation()); NestPath B = GeometryUtil.rotatePolygon2Polygon(pair.getB(), pair.getKey().getBrotation()); List <NestPath> nfp; if (pair.getKey().isInside()) { if (GeometryUtil.isRectangle(A, 0.001)) { nfp = GeometryUtil.noFitPolygonRectangle(A, B); if (nfp == null) { } } else { nfp = GeometryUtil.noFitPolygon(A, B, true, searchEdges); } if (nfp != null && nfp.Count > 0) { for (int i = 0; i < nfp.Count; i++) { if (GeometryUtil.polygonArea(nfp[i]) > 0) { nfp[i].reverse(); } } } else { //Warning on null inner NFP } } else { int count = 0; if (searchEdges) { // NFP Generator TODO double scale contorl nfp = GeometryUtil.noFitPolygon(A, B, false, searchEdges); if (nfp == null) { } } else { nfp = GeometryUtil.minkowskiDifference(A, B); } // sanity check if (nfp == null || nfp.Count == 0) { return(null); } for (int i = 0; i < nfp.Count; i++) { if (!searchEdges || i == 0) { if (Math.Abs(GeometryUtil.polygonArea(nfp[i])) < Math.Abs(GeometryUtil.polygonArea(A))) { nfp.RemoveAt(i); return(null); } } } if (nfp.Count == 0) { return(null); } for (int i = 0; i < nfp.Count; i++) { if (GeometryUtil.polygonArea(nfp[i]) > 0) { nfp[i].reverse(); } if (i > 0) { if ((bool)GeometryUtil.pointInPolygon(nfp[i].get(0), nfp[0])) { if (GeometryUtil.polygonArea(nfp[i]) < 0) { nfp[i].reverse(); } } } } if (useHoles && A.getChildren().Count > 0) { Bound Bbounds = GeometryUtil.getPolygonBounds(B); for (int i = 0; i < A.getChildren().Count; i++) { Bound Abounds = GeometryUtil.getPolygonBounds(A.getChildren()[i]); if (Abounds.width > Bbounds.width && Abounds.height > Bbounds.height) { List <NestPath> cnfp = GeometryUtil.noFitPolygon(A.getChildren()[i], B, true, searchEdges); // ensure all interior NFPs have the same winding direction if (cnfp != null && cnfp.Count > 0) { for (int j = 0; j < cnfp.Count; j++) { if (GeometryUtil.polygonArea(cnfp[j]) < 0) { cnfp[j].reverse(); } nfp.Add(cnfp[j]); } } } } } } if (nfp == null) { } return(new ParallelData(pair.getKey(), nfp)); }
/** * 开始进行Nest计算 * @return */ public List <List <Placement> > startNest() { List <NestPath> tree = CommonUtil.BuildTree(parts, Config.CURVE_TOLERANCE); CommonUtil.offsetTree(tree, 0.5 * config.SPACING); binPath.config = config; foreach (NestPath nestPath in parts) { nestPath.config = config; } NestPath binPolygon = NestPath.cleanNestPath(binPath); Bound binBound = GeometryUtil.getPolygonBounds(binPolygon); if (config.SPACING > 0) { List <NestPath> offsetBin = CommonUtil.polygonOffset(binPolygon, -0.5 * config.SPACING); if (offsetBin.Count == 1) { binPolygon = offsetBin[0]; } } binPolygon.setId(-1); List <int> integers = checkIfCanBePlaced(binPolygon, tree); List <NestPath> safeTree = new List <NestPath>(); foreach (int i in integers) { safeTree.Add(tree[i]); } tree = safeTree; double xbinmax = binPolygon.get(0).x; double xbinmin = binPolygon.get(0).x; double ybinmax = binPolygon.get(0).y; double ybinmin = binPolygon.get(0).y; for (int i = 1; i < binPolygon.size(); i++) { if (binPolygon.get(i).x > xbinmax) { xbinmax = binPolygon.get(i).x; } else if (binPolygon.get(i).x < xbinmin) { xbinmin = binPolygon.get(i).x; } if (binPolygon.get(i).y > ybinmax) { ybinmax = binPolygon.get(i).y; } else if (binPolygon.get(i).y < ybinmin) { ybinmin = binPolygon.get(i).y; } } for (int i = 0; i < binPolygon.size(); i++) { binPolygon.get(i).x -= xbinmin; binPolygon.get(i).y -= ybinmin; } double binPolygonWidth = xbinmax - xbinmin; double binPolygonHeight = ybinmax - ybinmin; if (GeometryUtil.polygonArea(binPolygon) > 0) { binPolygon.reverse(); } /** * 确保为逆时针 */ for (int i = 0; i < tree.Count; i++) { Segment start = tree[i].get(0); Segment end = tree[i].get(tree[i].size() - 1); if (start == end || GeometryUtil.almostEqual(start.x, end.x) && GeometryUtil.almostEqual(start.y, end.y)) { tree[i].pop(); } if (GeometryUtil.polygonArea(tree[i]) > 0) { tree[i].reverse(); } } launchcount = 0; Result best = null; // Tree Modification based on nest4J //List<NestPath> modifiedTree = new List<NestPath>(); //for (int i = 0; i < tree.Count; i++) //{ // List<Segment> modifiedSegment = new List<Segment>(); // NestPath currentTree = tree[i]; // List<Segment> currentTreeSegments = currentTree.getSegments(); // modifiedSegment.Add(currentTreeSegments[currentTreeSegments.Count-1]); // for (int j = 0; j < currentTreeSegments.Count-1; j++) // { // modifiedSegment.Add(currentTreeSegments[j]); // } // currentTree.setSegments(modifiedSegment); // modifiedTree.Add(currentTree); //} //tree = modifiedTree; for (int i = 0; i < loopCount; i++) { Result result = launchWorkers(tree, binPolygon, config); if (i == 0) { best = result; } else { if (best.fitness > result.fitness) { best = result; } } } double sumarea = 0; double totalarea = 0; for (int i = 0; i < best.placements.Count; i++) { totalarea += Math.Abs(GeometryUtil.polygonArea(binPolygon)); for (int j = 0; j < best.placements[i].Count; j++) { try { sumarea += Math.Abs(GeometryUtil.polygonArea(tree[best.placements[i][j].id])); } catch (Exception ex) { } } } double rate = (sumarea / totalarea) * 100; List <List <Placement> > appliedPlacement = applyPlacement(best, tree); return(appliedPlacement); }