|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Xml.Linq;
|
|
|
|
|
|
|
|
|
|
|
|
namespace DeepNestLib
|
|
|
|
|
|
{
|
|
|
|
|
|
public class NestingContext
|
|
|
|
|
|
{
|
|
|
|
|
|
public bool NeedStop
|
|
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Nest != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Nest.NeedStop;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Nest != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Nest.NeedStop = value;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
public List<NFP> Polygons { get; private set; } = new List<NFP>();
|
|
|
|
|
|
public List<NFP> Sheets { get; private set; } = new List<NFP>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public double MaterialUtilization { get; private set; } = 0;
|
|
|
|
|
|
public int PlacedPartsCount { get; private set; } = 0;
|
|
|
|
|
|
|
|
|
|
|
|
SheetPlacement current = null;
|
|
|
|
|
|
public SheetPlacement Current { get { return current; } }
|
|
|
|
|
|
public SvgNest Nest { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
public int Iterations { get; private set; } = 0;
|
|
|
|
|
|
public Dictionary<string, int> PartsStatistic { get; set; } = new Dictionary<string, int>();
|
|
|
|
|
|
public SvgNestConfig Config { get; set; } = new SvgNestConfig();
|
|
|
|
|
|
|
|
|
|
|
|
public Action<float> DisplayProgressAction
|
|
|
|
|
|
{
|
|
|
|
|
|
get;
|
|
|
|
|
|
set;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//Background background;
|
|
|
|
|
|
public void StartNest()
|
|
|
|
|
|
{
|
|
|
|
|
|
current = null;
|
|
|
|
|
|
Nest = new SvgNest(this.Config);
|
|
|
|
|
|
Nest.DisplayProgressAction = DisplayProgressAction;
|
|
|
|
|
|
//Nest.background = background;
|
|
|
|
|
|
////background = new Background();
|
|
|
|
|
|
//background.cacheProcess = new Dictionary<string, NFP[]>();
|
|
|
|
|
|
//background.window = new windowUnk();
|
|
|
|
|
|
//background.callCounter = 0;
|
|
|
|
|
|
//background.DisplayProgressAction = DisplayProgressAction;
|
|
|
|
|
|
Iterations = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool offsetTreePhase = true;
|
|
|
|
|
|
public void NestIterate()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (NeedStop == true)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
List<NFP> lsheets = new List<NFP>();
|
|
|
|
|
|
List<NFP> lpoly = new List<NFP>();
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < Polygons.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Polygons[i].id = i;
|
|
|
|
|
|
//string strData = IOHelp.SvgPts2Dfd(Polygons[0].Points);
|
|
|
|
|
|
//Trace.WriteLine($"Polygon {i}:{strData}");
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int i = 0; i < Sheets.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Sheets[i].id = i;
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach (var item in Polygons)
|
|
|
|
|
|
{
|
|
|
|
|
|
NFP clone = new NFP();
|
|
|
|
|
|
clone.id = item.id;
|
|
|
|
|
|
clone.Name = item.Name;
|
|
|
|
|
|
clone.source = item.source;
|
|
|
|
|
|
clone.Points = item.Points.Select(z => new SvgPoint(z.x, z.y) { exact = z.exact }).ToList();
|
|
|
|
|
|
if (item.children != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
clone.children = new List<NFP>();
|
|
|
|
|
|
foreach (var citem in item.children)
|
|
|
|
|
|
{
|
|
|
|
|
|
clone.children.Add(new NFP());
|
|
|
|
|
|
var l = clone.children.Last();
|
|
|
|
|
|
l.id = citem.id;
|
|
|
|
|
|
l.Name = citem.Name;
|
|
|
|
|
|
l.source = citem.source;
|
|
|
|
|
|
l.Points = citem.Points.Select(z => new SvgPoint(z.x, z.y) { exact = z.exact }).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
lpoly.Add(clone);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var item in Sheets)
|
|
|
|
|
|
{
|
|
|
|
|
|
NFP clone = new NFP();
|
|
|
|
|
|
clone.id = item.id;
|
|
|
|
|
|
clone.Name = item.Name;
|
|
|
|
|
|
clone.source = item.source;
|
|
|
|
|
|
clone.Points = item.Points.Select(z => new SvgPoint(z.x, z.y) { exact = z.exact }).ToList();
|
|
|
|
|
|
if (item.children != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
clone.children = new List<NFP>();
|
|
|
|
|
|
foreach (var citem in item.children)
|
|
|
|
|
|
{
|
|
|
|
|
|
clone.children.Add(new NFP());
|
|
|
|
|
|
var l = clone.children.Last();
|
|
|
|
|
|
l.id = citem.id;
|
|
|
|
|
|
l.Name = citem.Name;
|
|
|
|
|
|
l.source = citem.source;
|
|
|
|
|
|
l.Points = citem.Points.Select(z => new SvgPoint(z.x, z.y) { exact = z.exact }).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
lsheets.Add(clone);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (NeedStop == true)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
float progress = 0.11f;
|
|
|
|
|
|
DisplayProgressAction?.Invoke(progress);
|
|
|
|
|
|
if (offsetTreePhase)
|
|
|
|
|
|
{
|
|
|
|
|
|
var grps = lpoly.GroupBy(z => z.source).ToArray();
|
|
|
|
|
|
if (Background.UseParallel)
|
|
|
|
|
|
{
|
|
|
|
|
|
Parallel.ForEach(grps, (item) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
SvgNest.offsetTree(item.First(), 0.5 * this.Config.spacing, this.Config);
|
|
|
|
|
|
foreach (var zitem in item)
|
|
|
|
|
|
{
|
|
|
|
|
|
zitem.Points = new List<SvgPoint>(item.First().Points);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var item in grps)
|
|
|
|
|
|
{
|
|
|
|
|
|
SvgNest.offsetTree(item.First(), 0.5 * this.Config.spacing , this.Config);
|
|
|
|
|
|
foreach (var zitem in item)
|
|
|
|
|
|
{
|
|
|
|
|
|
zitem.Points = new List<SvgPoint>(item.First().Points);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var item in lsheets)
|
|
|
|
|
|
{
|
|
|
|
|
|
var gap = this.Config.sheetSpacing - this.Config.spacing / 2;
|
|
|
|
|
|
SvgNest.offsetTree(item, -gap, this.Config, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (NeedStop == true)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
progress = 0.22f;
|
|
|
|
|
|
DisplayProgressAction?.Invoke(progress);
|
|
|
|
|
|
List<NestItem> partsLocal = new List<NestItem>();
|
|
|
|
|
|
var p1 = lpoly.GroupBy(z => z.source).Select(z => new NestItem()
|
|
|
|
|
|
{
|
|
|
|
|
|
Polygon = z.First(),
|
|
|
|
|
|
IsSheet = false,
|
|
|
|
|
|
Quanity = z.Count()
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
var p2 = lsheets.GroupBy(z => z.source).Select(z => new NestItem()
|
|
|
|
|
|
{
|
|
|
|
|
|
Polygon = z.First(),
|
|
|
|
|
|
IsSheet = true,
|
|
|
|
|
|
Quanity = z.Count()
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
partsLocal.AddRange(p1);
|
|
|
|
|
|
partsLocal.AddRange(p2);
|
|
|
|
|
|
int srcc = 0;
|
|
|
|
|
|
foreach (var item in partsLocal)
|
|
|
|
|
|
{
|
|
|
|
|
|
item.Polygon.source = srcc++;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (NeedStop == true)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
Nest.launchWorkers(partsLocal.ToArray());
|
|
|
|
|
|
var plcpr = Nest.nests.First();
|
|
|
|
|
|
|
|
|
|
|
|
if (current == null || plcpr.fitness < current.fitness)
|
|
|
|
|
|
{
|
|
|
|
|
|
AssignPlacement(plcpr);
|
|
|
|
|
|
}
|
|
|
|
|
|
Iterations++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Export(string v)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (v.ToLower().EndsWith("svg"))
|
|
|
|
|
|
SvgParser.Export(v, Polygons, Sheets);
|
|
|
|
|
|
else if (v.ToLower().EndsWith("dxf"))
|
|
|
|
|
|
DxfParser.Export(v, Polygons, Sheets);
|
|
|
|
|
|
else
|
|
|
|
|
|
throw new NotImplementedException($"unknown format: {v}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AssignPlacement(SheetPlacement plcpr)
|
|
|
|
|
|
{
|
|
|
|
|
|
current = plcpr;
|
|
|
|
|
|
double totalSheetsArea = 0;
|
|
|
|
|
|
double totalPartsArea = 0;
|
|
|
|
|
|
|
|
|
|
|
|
PlacedPartsCount = 0;
|
|
|
|
|
|
List<NFP> placed = new List<NFP>();
|
|
|
|
|
|
foreach (var item in Polygons)
|
|
|
|
|
|
{
|
|
|
|
|
|
item.sheet = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
List<int> sheetsIds = new List<int>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var item in plcpr.placements)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var zitem in item)
|
|
|
|
|
|
{
|
|
|
|
|
|
var sheetid = zitem.sheetId;
|
|
|
|
|
|
if (!sheetsIds.Contains(sheetid))
|
|
|
|
|
|
{
|
|
|
|
|
|
sheetsIds.Add(sheetid);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var sheet = Sheets.First(z => z.id == sheetid);
|
|
|
|
|
|
totalSheetsArea += GeometryUtil.polygonArea(sheet);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var ssitem in zitem.sheetplacements)
|
|
|
|
|
|
{
|
|
|
|
|
|
PlacedPartsCount++;
|
|
|
|
|
|
var poly = Polygons.First(z => z.id == ssitem.id);
|
|
|
|
|
|
totalPartsArea += GeometryUtil.polygonArea(poly);
|
|
|
|
|
|
placed.Add(poly);
|
|
|
|
|
|
poly.sheet = sheet;
|
|
|
|
|
|
poly.x = ssitem.x + sheet.x;
|
|
|
|
|
|
poly.y = ssitem.y + sheet.y;
|
|
|
|
|
|
poly.rotation = ssitem.rotation;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var emptySheets = Sheets.Where(z => !sheetsIds.Contains(z.id)).ToArray();
|
|
|
|
|
|
|
|
|
|
|
|
MaterialUtilization = Math.Abs(totalPartsArea / totalSheetsArea);
|
|
|
|
|
|
|
|
|
|
|
|
// 区分并统计
|
|
|
|
|
|
PartsStatistic = new Dictionary<string, int>();
|
|
|
|
|
|
foreach (var item in Polygons)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (item.fitted == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
item.x = -1000;
|
|
|
|
|
|
item.y = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
int nCount = 0;
|
|
|
|
|
|
if(PartsStatistic.TryGetValue(item.Name, out nCount))
|
|
|
|
|
|
{
|
|
|
|
|
|
PartsStatistic[item.Name] = (nCount + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
PartsStatistic.Add(item.Name, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//var ppps = Polygons.Where(z => !placed.Contains(z));
|
|
|
|
|
|
//foreach (var item in ppps)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// item.x = -1000;
|
|
|
|
|
|
// item.y = 0;
|
|
|
|
|
|
//}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void ReorderSheets()
|
|
|
|
|
|
{
|
|
|
|
|
|
double x = 0;
|
|
|
|
|
|
double y = 0;
|
|
|
|
|
|
int gap = 10;
|
|
|
|
|
|
for (int i = 0; i < Sheets.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
Sheets[i].x = x;
|
|
|
|
|
|
Sheets[i].y = y;
|
|
|
|
|
|
if (Sheets[i] is Sheet)
|
|
|
|
|
|
{
|
|
|
|
|
|
var r = Sheets[i] as Sheet;
|
|
|
|
|
|
x += r.Width + gap;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var maxx = Sheets[i].Points.Max(z => z.x);
|
|
|
|
|
|
var minx = Sheets[i].Points.Min(z => z.x);
|
|
|
|
|
|
var w = maxx - minx;
|
|
|
|
|
|
x += w + gap;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void AddSheet(int w, int h, int src)
|
|
|
|
|
|
{
|
|
|
|
|
|
var tt = new RectangleSheet();
|
|
|
|
|
|
tt.Name = "sheet" + (Sheets.Count + 1);
|
|
|
|
|
|
Sheets.Add(tt);
|
|
|
|
|
|
|
|
|
|
|
|
tt.source = src;
|
|
|
|
|
|
tt.Height = h;
|
|
|
|
|
|
tt.Width = w;
|
|
|
|
|
|
tt.Rebuild();
|
|
|
|
|
|
ReorderSheets();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Random r = new Random();
|
|
|
|
|
|
|
|
|
|
|
|
public NestingContext()
|
|
|
|
|
|
{
|
|
|
|
|
|
//background = new Background();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void LoadSampleData()
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine("Adding sheets..");
|
|
|
|
|
|
//add sheets
|
|
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
AddSheet(3000, 1500, 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Console.WriteLine("Adding parts..");
|
|
|
|
|
|
//add parts
|
|
|
|
|
|
int src1 = GetNextSource();
|
|
|
|
|
|
for (int i = 0; i < 200; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
AddRectanglePart(src1, 250, 220);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
public void LoadInputData(string path, int count)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dir = new DirectoryInfo(path);
|
|
|
|
|
|
foreach (var item in dir.GetFiles("*.svg"))
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var src = GetNextSource();
|
|
|
|
|
|
for (int i = 0; i < count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ImportFromRawDetail(SvgParser.LoadSvg(item.FullName), src);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine("Error loading " + item.FullName + ". skip");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public NFP ImportFromRawDetail(RawDetail raw, int src)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = raw.ToNfp();
|
|
|
|
|
|
if (d == null) return null;
|
|
|
|
|
|
d.source = src;
|
|
|
|
|
|
Polygons.Add(d);
|
|
|
|
|
|
return d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public int GetNextSource()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Polygons.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
return Polygons.Max(z => z.source.Value) + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public int GetNextSheetSource()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (Sheets.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
return Sheets.Max(z => z.source.Value) + 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
public void AddRectanglePart(int src, int ww = 50, int hh = 80)
|
|
|
|
|
|
{
|
|
|
|
|
|
int xx = 0;
|
|
|
|
|
|
int yy = 0;
|
|
|
|
|
|
NFP pl = new NFP();
|
|
|
|
|
|
|
|
|
|
|
|
Polygons.Add(pl);
|
|
|
|
|
|
pl.source = src;
|
|
|
|
|
|
pl.Points = new List<SvgPoint>() { };
|
|
|
|
|
|
pl.AddPoint(new SvgPoint(xx, yy));
|
|
|
|
|
|
pl.AddPoint(new SvgPoint(xx + ww, yy));
|
|
|
|
|
|
pl.AddPoint(new SvgPoint(xx + ww, yy + hh));
|
|
|
|
|
|
pl.AddPoint(new SvgPoint(xx, yy + hh));
|
|
|
|
|
|
}
|
|
|
|
|
|
public void LoadXml(string v)
|
|
|
|
|
|
{
|
|
|
|
|
|
var d = XDocument.Load(v);
|
|
|
|
|
|
var f = d.Descendants().First();
|
|
|
|
|
|
var gap = int.Parse(f.Attribute("gap").Value);
|
|
|
|
|
|
this.Config.spacing = gap;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var item in d.Descendants("sheet"))
|
|
|
|
|
|
{
|
|
|
|
|
|
int src = GetNextSheetSource();
|
|
|
|
|
|
var cnt = int.Parse(item.Attribute("count").Value);
|
|
|
|
|
|
var ww = int.Parse(item.Attribute("width").Value);
|
|
|
|
|
|
var hh = int.Parse(item.Attribute("height").Value);
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < cnt; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
AddSheet(ww, hh, src);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
foreach (var item in d.Descendants("part"))
|
|
|
|
|
|
{
|
|
|
|
|
|
var cnt = int.Parse(item.Attribute("count").Value);
|
|
|
|
|
|
var path = item.Attribute("path").Value;
|
|
|
|
|
|
RawDetail r = null;
|
|
|
|
|
|
if (path.ToLower().EndsWith("svg"))
|
|
|
|
|
|
{
|
|
|
|
|
|
r = SvgParser.LoadSvg(path);
|
|
|
|
|
|
}
|
|
|
|
|
|
else if (path.ToLower().EndsWith("dxf"))
|
|
|
|
|
|
{
|
|
|
|
|
|
r = DxfParser.LoadDxf(path);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var src = GetNextSource();
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < cnt; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
ImportFromRawDetail(r, src);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|