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(); }
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 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); }
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 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); }
/** * 根据板件列表与旋转角列表,通过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)); }
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); }