You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
15 KiB
C#

1 month ago
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);
}
}
}
}
}