refactor: extract OpenNest.IO class library from WinForms project

Move DxfImporter, DxfExporter, NestReader, NestWriter, ProgramReader,
and Extensions into a new OpenNest.IO class library. The WinForms project
now references OpenNest.IO instead of ACadSharp directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 15:41:07 -04:00
parent 4196a30791
commit cc286dd9b7
9 changed files with 26 additions and 1 deletions

View File

@@ -1,300 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using ACadSharp;
using ACadSharp.Entities;
using ACadSharp.IO;
using ACadSharp.Tables;
using CSMath;
using OpenNest.CNC;
using OpenNest.Math;
namespace OpenNest.IO
{
using AcadArc = ACadSharp.Entities.Arc;
using AcadCircle = ACadSharp.Entities.Circle;
using AcadLine = ACadSharp.Entities.Line;
using Layer = ACadSharp.Tables.Layer;
public class DxfExporter
{
private CadDocument doc;
private XYZ curpos;
private Mode mode;
private readonly Layer cutLayer;
private readonly Layer rapidLayer;
private readonly Layer plateLayer;
public DxfExporter()
{
doc = new CadDocument();
cutLayer = new Layer("Cut");
cutLayer.Color = new Color(1);
rapidLayer = new Layer("Rapid");
rapidLayer.Color = new Color(5);
plateLayer = new Layer("Plate");
plateLayer.Color = new Color(4);
}
public void ExportProgram(Program program, Stream stream)
{
doc = new CadDocument();
EnsureLayers();
AddProgram(program);
using (var writer = new DxfWriter(stream, doc, false))
{
writer.Write();
}
}
public bool ExportProgram(Program program, string path)
{
Stream stream = null;
var success = false;
try
{
stream = File.Create(path);
ExportProgram(program, stream);
success = true;
}
catch
{
Debug.Fail("DxfExporter.ExportProgram failed to write program to file: " + path);
}
finally
{
if (stream != null)
stream.Close();
}
return success;
}
public void ExportPlate(Plate plate, Stream stream)
{
doc = new CadDocument();
EnsureLayers();
AddPlateOutline(plate);
foreach (var part in plate.Parts)
{
var endpt = part.Location.ToAcadXYZ();
AddLine(curpos, endpt, rapidLayer);
curpos = part.Location.ToAcadXYZ();
AddProgram(part.Program);
}
using (var writer = new DxfWriter(stream, doc, false))
{
writer.Write();
}
}
public bool ExportPlate(Plate plate, string path)
{
Stream stream = null;
var success = false;
try
{
stream = File.Create(path);
ExportPlate(plate, stream);
success = true;
}
catch
{
Debug.Fail("DxfExporter.ExportPlate failed to write plate to file: " + path);
}
finally
{
if (stream != null)
stream.Close();
}
return success;
}
private void EnsureLayers()
{
doc.Layers.Add(cutLayer);
doc.Layers.Add(rapidLayer);
doc.Layers.Add(plateLayer);
}
private void AddLine(XYZ start, XYZ end, Layer layer)
{
var ln = new AcadLine();
ln.StartPoint = start;
ln.EndPoint = end;
ln.Layer = layer;
doc.Entities.Add(ln);
}
private void AddPlateOutline(Plate plate)
{
XYZ pt1;
XYZ pt2;
XYZ pt3;
XYZ pt4;
switch (plate.Quadrant)
{
case 1:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, plate.Size.Height, 0);
pt3 = new XYZ(plate.Size.Width, plate.Size.Height, 0);
pt4 = new XYZ(plate.Size.Width, 0, 0);
break;
case 2:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, plate.Size.Height, 0);
pt3 = new XYZ(-plate.Size.Width, plate.Size.Height, 0);
pt4 = new XYZ(-plate.Size.Width, 0, 0);
break;
case 3:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, -plate.Size.Height, 0);
pt3 = new XYZ(-plate.Size.Width, -plate.Size.Height, 0);
pt4 = new XYZ(-plate.Size.Width, 0, 0);
break;
case 4:
pt1 = new XYZ(0, 0, 0);
pt2 = new XYZ(0, -plate.Size.Height, 0);
pt3 = new XYZ(plate.Size.Width, -plate.Size.Height, 0);
pt4 = new XYZ(plate.Size.Width, 0, 0);
break;
default:
return;
}
AddLine(pt1, pt2, plateLayer);
AddLine(pt2, pt3, plateLayer);
AddLine(pt3, pt4, plateLayer);
AddLine(pt4, pt1, plateLayer);
var m1 = new XYZ(pt1.X + plate.EdgeSpacing.Left, pt1.Y + plate.EdgeSpacing.Bottom, 0);
var m2 = new XYZ(m1.X, pt2.Y - plate.EdgeSpacing.Top, 0);
var m3 = new XYZ(pt3.X - plate.EdgeSpacing.Right, m2.Y, 0);
var m4 = new XYZ(m3.X, m1.Y, 0);
AddLine(m1, m2, plateLayer);
AddLine(m2, m3, plateLayer);
AddLine(m3, m4, plateLayer);
AddLine(m4, m1, plateLayer);
}
private void AddProgram(Program program)
{
mode = program.Mode;
for (var i = 0; i < program.Length; ++i)
{
var code = program[i];
switch (code.Type)
{
case CodeType.ArcMove:
var arc = (ArcMove)code;
AddArcMove(arc);
break;
case CodeType.LinearMove:
var line = (LinearMove)code;
AddLinearMove(line);
break;
case CodeType.RapidMove:
var rapid = (RapidMove)code;
AddRapidMove(rapid);
break;
case CodeType.SubProgramCall:
var tmpmode = mode;
var subpgm = (CNC.SubProgramCall)code;
AddProgram(subpgm.Program);
mode = tmpmode;
break;
}
}
}
private void AddLinearMove(LinearMove line)
{
var pt = line.EndPoint.ToAcadXYZ();
if (mode == Mode.Incremental)
pt = new XYZ(pt.X + curpos.X, pt.Y + curpos.Y, 0);
AddLine(curpos, pt, cutLayer);
curpos = pt;
}
private void AddRapidMove(RapidMove rapid)
{
var pt = rapid.EndPoint.ToAcadXYZ();
if (mode == Mode.Incremental)
pt = new XYZ(pt.X + curpos.X, pt.Y + curpos.Y, 0);
AddLine(curpos, pt, rapidLayer);
curpos = pt;
}
private void AddArcMove(ArcMove arc)
{
var center = arc.CenterPoint.ToAcadXYZ();
var endpt = arc.EndPoint.ToAcadXYZ();
if (mode == Mode.Incremental)
{
endpt = new XYZ(endpt.X + curpos.X, endpt.Y + curpos.Y, 0);
center = new XYZ(center.X + curpos.X, center.Y + curpos.Y, 0);
}
var startAngle = System.Math.Atan2(
curpos.Y - center.Y,
curpos.X - center.X);
var endAngle = System.Math.Atan2(
endpt.Y - center.Y,
endpt.X - center.X);
if (arc.Rotation == OpenNest.RotationType.CW)
Generic.Swap(ref startAngle, ref endAngle);
var dx = endpt.X - center.X;
var dy = endpt.Y - center.Y;
var radius = System.Math.Sqrt(dx * dx + dy * dy);
if (startAngle.IsEqualTo(endAngle))
{
var circle = new AcadCircle();
circle.Center = center;
circle.Radius = radius;
circle.Layer = cutLayer;
doc.Entities.Add(circle);
}
else
{
var arc2 = new AcadArc();
arc2.Center = center;
arc2.Radius = radius;
arc2.StartAngle = startAngle;
arc2.EndAngle = endAngle;
arc2.Layer = cutLayer;
doc.Entities.Add(arc2);
}
curpos = endpt;
}
}
}

View File

@@ -1,117 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using ACadSharp;
using ACadSharp.IO;
using OpenNest.Geometry;
namespace OpenNest.IO
{
public class DxfImporter
{
public int SplinePrecision { get; set; }
public DxfImporter()
{
}
private List<Entity> GetGeometry(CadDocument doc)
{
var entities = new List<Entity>();
var lines = new List<Line>();
var arcs = new List<Arc>();
foreach (var entity in doc.Entities)
{
switch (entity)
{
case ACadSharp.Entities.Line line:
lines.Add(line.ToOpenNest());
break;
case ACadSharp.Entities.Arc arc:
arcs.Add(arc.ToOpenNest());
break;
case ACadSharp.Entities.Circle circle:
entities.Add(circle.ToOpenNest());
break;
case ACadSharp.Entities.Spline spline:
lines.AddRange(spline.ToOpenNest());
break;
case ACadSharp.Entities.LwPolyline lwPolyline:
lines.AddRange(lwPolyline.ToOpenNest());
break;
case ACadSharp.Entities.Polyline polyline:
lines.AddRange(polyline.ToOpenNest());
break;
case ACadSharp.Entities.Ellipse ellipse:
lines.AddRange(ellipse.ToOpenNest(SplinePrecision));
break;
}
}
Helper.Optimize(lines);
Helper.Optimize(arcs);
entities.AddRange(lines);
entities.AddRange(arcs);
return entities;
}
public bool GetGeometry(Stream stream, out List<Entity> geometry)
{
var success = false;
try
{
using (var reader = new DxfReader(stream))
{
var doc = reader.Read();
geometry = GetGeometry(doc);
success = true;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
geometry = new List<Entity>();
}
finally
{
if (stream != null)
stream.Close();
}
return success;
}
public bool GetGeometry(string path, out List<Entity> geometry)
{
var success = false;
try
{
using (var reader = new DxfReader(path))
{
var doc = reader.Read();
geometry = GetGeometry(doc);
success = true;
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
geometry = new List<Entity>();
}
return success;
}
}
}

View File

@@ -1,276 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using ACadSharp.Entities;
using CSMath;
using OpenNest.Geometry;
namespace OpenNest.IO
{
internal static class Extensions
{
public static Vector ToOpenNest(this XY v)
{
return new Vector(v.X, v.Y);
}
public static Vector ToOpenNest(this XYZ v)
{
return new Vector(v.X, v.Y);
}
public static Geometry.Arc ToOpenNest(this ACadSharp.Entities.Arc arc)
{
var result = new Geometry.Arc(
arc.Center.X, arc.Center.Y, arc.Radius,
arc.StartAngle,
arc.EndAngle)
{
Layer = arc.Layer.ToOpenNest()
};
result.ApplyDxfProperties(arc);
return result;
}
public static Geometry.Circle ToOpenNest(this ACadSharp.Entities.Circle circle)
{
var result = new Geometry.Circle(
circle.Center.X, circle.Center.Y,
circle.Radius)
{
Layer = circle.Layer.ToOpenNest()
};
result.ApplyDxfProperties(circle);
return result;
}
public static Geometry.Line ToOpenNest(this ACadSharp.Entities.Line line)
{
var result = new Geometry.Line(
line.StartPoint.X, line.StartPoint.Y,
line.EndPoint.X, line.EndPoint.Y)
{
Layer = line.Layer.ToOpenNest()
};
result.ApplyDxfProperties(line);
return result;
}
public static List<Geometry.Line> ToOpenNest(this Spline spline)
{
var lines = new List<Geometry.Line>();
var pts = spline.ControlPoints;
if (pts.Count == 0)
return lines;
var layer = spline.Layer.ToOpenNest();
var color = spline.ResolveColor();
var lineTypeName = spline.ResolveLineTypeName();
var lastPoint = pts[0].ToOpenNest();
for (var i = 1; i < pts.Count; i++)
{
var nextPoint = pts[i].ToOpenNest();
lines.Add(new Geometry.Line(lastPoint, nextPoint)
{
Layer = layer, Color = color, LineTypeName = lineTypeName
});
lastPoint = nextPoint;
}
if (spline.IsClosed)
lines.Add(new Geometry.Line(lastPoint, pts[0].ToOpenNest())
{
Layer = layer, Color = color, LineTypeName = lineTypeName
});
return lines;
}
public static List<Geometry.Line> ToOpenNest(this Polyline polyline)
{
var lines = new List<Geometry.Line>();
if (polyline.Vertices.Count == 0)
return lines;
var layer = polyline.Layer.ToOpenNest();
var color = polyline.ResolveColor();
var lineTypeName = polyline.ResolveLineTypeName();
var lastPoint = polyline.Vertices[0].Location.ToOpenNest();
for (var i = 1; i < polyline.Vertices.Count; i++)
{
var nextPoint = polyline.Vertices[i].Location.ToOpenNest();
lines.Add(new Geometry.Line(lastPoint, nextPoint)
{
Layer = layer, Color = color, LineTypeName = lineTypeName
});
lastPoint = nextPoint;
}
var isClosed = (polyline.Flags & PolylineFlags.ClosedPolylineOrClosedPolygonMeshInM) != 0;
if (isClosed)
lines.Add(new Geometry.Line(lastPoint, polyline.Vertices[0].Location.ToOpenNest())
{
Layer = layer, Color = color, LineTypeName = lineTypeName
});
return lines;
}
public static List<Geometry.Line> ToOpenNest(this LwPolyline polyline)
{
var lines = new List<Geometry.Line>();
if (polyline.Vertices.Count == 0)
return lines;
var layer = polyline.Layer.ToOpenNest();
var color = polyline.ResolveColor();
var lineTypeName = polyline.ResolveLineTypeName();
var lastPoint = polyline.Vertices[0].ToOpenNest();
for (var i = 1; i < polyline.Vertices.Count; i++)
{
var nextPoint = polyline.Vertices[i].ToOpenNest();
lines.Add(new Geometry.Line(lastPoint, nextPoint)
{
Layer = layer, Color = color, LineTypeName = lineTypeName
});
lastPoint = nextPoint;
}
var isClosed = (polyline.Flags & LwPolylineFlags.Closed) != 0;
if (isClosed)
lines.Add(new Geometry.Line(lastPoint, polyline.Vertices[0].ToOpenNest())
{
Layer = layer, Color = color, LineTypeName = lineTypeName
});
return lines;
}
public static List<Geometry.Line> ToOpenNest(this ACadSharp.Entities.Ellipse ellipse, int precision = 200)
{
var lines = new List<Geometry.Line>();
var center = new Vector(ellipse.Center.X, ellipse.Center.Y);
var majorAxis = new Vector(ellipse.MajorAxisEndPoint.X, ellipse.MajorAxisEndPoint.Y);
var majorLength = System.Math.Sqrt(majorAxis.X * majorAxis.X + majorAxis.Y * majorAxis.Y);
var minorLength = majorLength * ellipse.RadiusRatio;
var rotation = System.Math.Atan2(majorAxis.Y, majorAxis.X);
var startParam = ellipse.StartParameter;
var endParam = ellipse.EndParameter;
if (endParam <= startParam)
endParam += System.Math.PI * 2.0;
var step = (endParam - startParam) / precision;
var points = new List<Vector>();
for (var i = 0; i <= precision; i++)
{
var t = startParam + step * i;
var x = majorLength * System.Math.Cos(t);
var y = minorLength * System.Math.Sin(t);
// Rotate by the major axis angle and translate to center
var cos = System.Math.Cos(rotation);
var sin = System.Math.Sin(rotation);
var px = center.X + x * cos - y * sin;
var py = center.Y + x * sin + y * cos;
points.Add(new Vector(px, py));
}
var layer = ellipse.Layer.ToOpenNest();
var color = ellipse.ResolveColor();
var lineTypeName = ellipse.ResolveLineTypeName();
for (var i = 0; i < points.Count - 1; i++)
{
lines.Add(new Geometry.Line(points[i], points[i + 1])
{
Layer = layer, Color = color, LineTypeName = lineTypeName
});
}
// Close the ellipse if it's a full ellipse
if (lines.Count >= 2)
{
var first = lines.First();
var last = lines.Last();
lines.Add(new Geometry.Line(last.EndPoint, first.StartPoint)
{
Layer = layer, Color = color, LineTypeName = lineTypeName
});
}
return lines;
}
public static Geometry.Layer ToOpenNest(this ACadSharp.Tables.Layer layer)
{
return new Geometry.Layer(layer.Name)
{
Color = Color.FromArgb(layer.Color.R, layer.Color.G, layer.Color.B),
IsVisible = layer.IsOn,
LineTypeName = layer.LineType?.Name
};
}
public static Color ResolveColor(this ACadSharp.Entities.Entity entity)
{
var color = entity.Color;
if (color.IsByLayer)
color = entity.Layer.Color;
return Color.FromArgb(color.R, color.G, color.B);
}
public static string ResolveLineTypeName(this ACadSharp.Entities.Entity entity)
{
var lt = entity.LineType;
if (lt == null || string.Equals(lt.Name, "ByLayer", System.StringComparison.OrdinalIgnoreCase))
return entity.Layer.LineType?.Name ?? "Continuous";
return lt.Name;
}
public static void ApplyDxfProperties(this Geometry.Entity target, ACadSharp.Entities.Entity source)
{
target.Color = source.ResolveColor();
target.LineTypeName = source.ResolveLineTypeName();
}
public static Vector ToOpenNest(this LwPolyline.Vertex v)
{
return new Vector(v.Location.X, v.Location.Y);
}
public static XY ToAcad(this Vector v)
{
return new XY(v.X, v.Y);
}
public static XYZ ToAcadXYZ(this Vector v)
{
return new XYZ(v.X, v.Y, 0);
}
}
}

View File

@@ -1,473 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using OpenNest.CNC;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.IO
{
public sealed class NestReader
{
private ZipArchive zipArchive;
private Dictionary<int, Plate> plateDict;
private Dictionary<int, Drawing> drawingDict;
private Dictionary<int, Program> programDict;
private Dictionary<int, Program> plateProgramDict;
private Stream stream;
private Nest nest;
private NestReader()
{
plateDict = new Dictionary<int, Plate>();
drawingDict = new Dictionary<int, Drawing>();
programDict = new Dictionary<int, Program>();
plateProgramDict = new Dictionary<int, Program>();
nest = new Nest();
}
public NestReader(string file)
: this()
{
stream = new FileStream(file, FileMode.Open, FileAccess.Read);
zipArchive = new ZipArchive(stream, ZipArchiveMode.Read);
}
public NestReader(Stream stream)
: this()
{
this.stream = stream;
zipArchive = new ZipArchive(stream, ZipArchiveMode.Read);
}
public Nest Read()
{
const string plateExtensionPattern = "plate-\\d\\d\\d";
const string programExtensionPattern = "program-\\d\\d\\d";
foreach (var entry in zipArchive.Entries)
{
var memstream = new MemoryStream();
using (var entryStream = entry.Open())
{
entryStream.CopyTo(memstream);
}
memstream.Position = 0;
switch (entry.FullName)
{
case "info":
ReadNestInfo(memstream);
continue;
case "drawing-info":
ReadDrawingInfo(memstream);
continue;
case "plate-info":
ReadPlateInfo(memstream);
continue;
}
if (Regex.IsMatch(entry.FullName, programExtensionPattern))
{
ReadProgram(memstream, entry.FullName);
continue;
}
if (Regex.IsMatch(entry.FullName, plateExtensionPattern))
{
ReadPlate(memstream, entry.FullName);
continue;
}
}
LinkProgramsToDrawings();
LinkPartsToPlates();
AddPlatesToNest();
AddDrawingsToNest();
zipArchive.Dispose();
stream.Close();
return nest;
}
private void ReadNestInfo(Stream stream)
{
var reader = XmlReader.Create(stream);
var spacing = new Spacing();
while (reader.Read())
{
if (!reader.IsStartElement())
continue;
switch (reader.Name)
{
case "Nest":
nest.Name = reader["name"];
break;
case "Units":
Units units;
TryParseEnum<Units>(reader.ReadString(), out units);
nest.Units = units;
break;
case "Customer":
nest.Customer = reader.ReadString();
break;
case "DateCreated":
nest.DateCreated = DateTime.Parse(reader.ReadString());
break;
case "DateLastModified":
nest.DateLastModified = DateTime.Parse(reader.ReadString());
break;
case "Notes":
nest.Notes = Uri.UnescapeDataString(reader.ReadString());
break;
case "Size":
nest.PlateDefaults.Size = OpenNest.Geometry.Size.Parse(reader.ReadString());
break;
case "Thickness":
nest.PlateDefaults.Thickness = double.Parse(reader.ReadString());
break;
case "Quadrant":
nest.PlateDefaults.Quadrant = int.Parse(reader.ReadString());
break;
case "PartSpacing":
nest.PlateDefaults.PartSpacing = double.Parse(reader.ReadString());
break;
case "Name":
nest.PlateDefaults.Material.Name = reader.ReadString();
break;
case "Grade":
nest.PlateDefaults.Material.Grade = reader.ReadString();
break;
case "Density":
nest.PlateDefaults.Material.Density = double.Parse(reader.ReadString());
break;
case "Left":
spacing.Left = double.Parse(reader.ReadString());
break;
case "Right":
spacing.Right = double.Parse(reader.ReadString());
break;
case "Top":
spacing.Top = double.Parse(reader.ReadString());
break;
case "Bottom":
spacing.Bottom = double.Parse(reader.ReadString());
break;
}
}
reader.Close();
nest.PlateDefaults.EdgeSpacing = spacing;
}
private void ReadDrawingInfo(Stream stream)
{
var reader = XmlReader.Create(stream);
Drawing drawing = null;
while (reader.Read())
{
if (!reader.IsStartElement())
continue;
switch (reader.Name)
{
case "Drawing":
var id = int.Parse(reader["id"]);
var name = reader["name"];
drawingDict.Add(id, (drawing = new Drawing(name)));
break;
case "Customer":
drawing.Customer = reader.ReadString();
break;
case "Color":
{
var parts = reader.ReadString().Split(',');
if (parts.Length == 3)
{
byte r = byte.Parse(parts[0]);
byte g = byte.Parse(parts[1]);
byte b = byte.Parse(parts[2]);
drawing.Color = Color.FromArgb(r, g, b);
}
else if (parts.Length == 4)
{
byte a = byte.Parse(parts[0]);
byte r = byte.Parse(parts[1]);
byte g = byte.Parse(parts[2]);
byte b = byte.Parse(parts[3]);
drawing.Color = Color.FromArgb(a, r, g, b);
}
}
break;
case "Required":
drawing.Quantity.Required = int.Parse(reader.ReadString());
break;
case "Name":
drawing.Material.Name = reader.ReadString();
break;
case "Grade":
drawing.Material.Grade = reader.ReadString();
break;
case "Density":
drawing.Material.Density = double.Parse(reader.ReadString());
break;
case "Path":
drawing.Source.Path = reader.ReadString();
break;
case "Offset":
{
var parts = reader.ReadString().Split(',');
if (parts.Length != 2)
continue;
drawing.Source.Offset = new Vector(double.Parse(parts[0]), double.Parse(parts[1]));
}
break;
}
}
reader.Close();
}
private void ReadPlateInfo(Stream stream)
{
var reader = XmlReader.Create(stream);
var spacing = new Spacing();
Plate plate = null;
while (reader.Read())
{
if (!reader.IsStartElement())
continue;
switch (reader.Name)
{
case "Plate":
var id = int.Parse(reader["id"]);
if (plate != null)
plate.EdgeSpacing = spacing;
plateDict.Add(id, (plate = new Plate()));
break;
case "Size":
plate.Size = OpenNest.Geometry.Size.Parse(reader.ReadString());
break;
case "Qty":
plate.Quantity = int.Parse(reader.ReadString());
break;
case "Thickness":
plate.Thickness = double.Parse(reader.ReadString());
break;
case "Quadrant":
plate.Quadrant = int.Parse(reader.ReadString());
break;
case "PartSpacing":
plate.PartSpacing = double.Parse(reader.ReadString());
break;
case "Name":
plate.Material.Name = reader.ReadString();
break;
case "Grade":
plate.Material.Grade = reader.ReadString();
break;
case "Density":
plate.Material.Density = double.Parse(reader.ReadString());
break;
case "Left":
spacing.Left = double.Parse(reader.ReadString());
break;
case "Right":
spacing.Right = double.Parse(reader.ReadString());
break;
case "Top":
spacing.Top = double.Parse(reader.ReadString());
break;
case "Bottom":
spacing.Bottom = double.Parse(reader.ReadString());
break;
}
}
if (plate != null)
plate.EdgeSpacing = spacing;
}
private void ReadProgram(Stream stream, string name)
{
var id = GetProgramId(name);
var reader = new ProgramReader(stream);
var pgm = reader.Read();
programDict.Add(id, pgm);
}
private void ReadPlate(Stream stream, string name)
{
var id = GetPlateId(name);
var reader = new ProgramReader(stream);
var pgm = reader.Read();
plateProgramDict.Add(id, pgm);
}
private void LinkProgramsToDrawings()
{
foreach (var drawingItem in drawingDict)
{
Program pgm;
if (programDict.TryGetValue(drawingItem.Key, out pgm))
drawingItem.Value.Program = pgm;
}
}
private void LinkPartsToPlates()
{
foreach (var plateProgram in plateProgramDict)
{
var parts = CreateParts(plateProgram.Value);
Plate plate;
if (!plateDict.TryGetValue(plateProgram.Key, out plate))
plate = new Plate();
plate.Parts.AddRange(parts);
plateDict[plateProgram.Key] = plate;
}
}
private void AddPlatesToNest()
{
var plates = plateDict.OrderBy(i => i.Key).Select(i => i.Value).ToList();
nest.Plates.AddRange(plates);
}
private void AddDrawingsToNest()
{
var drawings = drawingDict.OrderBy(i => i.Key).Select(i => i.Value).ToList();
drawings.ForEach(d => nest.Drawings.Add(d));
}
private List<Part> CreateParts(Program pgm)
{
var parts = new List<Part>();
var pos = Vector.Zero;
for (int i = 0; i < pgm.Codes.Count; i++)
{
var code = pgm.Codes[i];
switch (code.Type)
{
case CodeType.RapidMove:
pos = ((RapidMove)code).EndPoint;
break;
case CodeType.SubProgramCall:
var subpgm = (SubProgramCall)code;
var dwg = drawingDict[subpgm.Id];
var part = new Part(dwg);
part.Rotate(Angle.ToRadians(subpgm.Rotation));
part.Offset(pos);
parts.Add(part);
break;
}
}
return parts;
}
private int GetPlateId(string name)
{
return int.Parse(name.Replace("plate-", ""));
}
private int GetProgramId(string name)
{
return int.Parse(name.Replace("program-", ""));
}
public static T ParseEnum<T>(string value)
{
return (T)Enum.Parse(typeof(T), value, true);
}
public static bool TryParseEnum<T>(string value, out T e)
{
try
{
e = ParseEnum<T>(value);
return true;
}
catch
{
e = ParseEnum<T>(typeof(T).GetEnumValues().GetValue(0).ToString());
}
return false;
}
private enum NestInfoSection
{
None,
DefaultPlate,
Material,
EdgeSpacing,
Source
}
}
}

View File

@@ -1,426 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Xml;
using OpenNest.CNC;
using OpenNest.Math;
namespace OpenNest.IO
{
public sealed class NestWriter
{
/// <summary>
/// Number of decimal places the output is round to.
/// This number must have more decimal places than Tolerance.Epsilon
/// </summary>
private const int OutputPrecision = 10;
/// <summary>
/// Fixed-point format string that avoids scientific notation.
/// ProgramReader treats 'E' as a code letter, so "6.66E-08" would be
/// split into X:"6.66" and E:"-08", corrupting the parsed value.
/// </summary>
private const string CoordinateFormat = "0.##########";
private readonly Nest nest;
private ZipArchive zipArchive;
private Dictionary<int, Drawing> drawingDict;
public NestWriter(Nest nest)
{
this.drawingDict = new Dictionary<int, Drawing>();
this.nest = nest;
}
public bool Write(string file)
{
this.nest.DateLastModified = DateTime.Now;
SetDrawingIds();
using (var fileStream = new FileStream(file, FileMode.Create))
using (zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create))
{
AddNestInfo();
AddPlates();
AddPlateInfo();
AddDrawings();
AddDrawingInfo();
}
return true;
}
private void SetDrawingIds()
{
int id = 1;
foreach (var drawing in nest.Drawings)
{
drawingDict.Add(id, drawing);
id++;
}
}
private void AddNestInfo()
{
var stream = new MemoryStream();
var writer = XmlWriter.Create(stream, new XmlWriterSettings()
{
Indent = true
});
writer.WriteStartDocument();
writer.WriteStartElement("Nest");
writer.WriteAttributeString("name", nest.Name);
writer.WriteElementString("Units", nest.Units.ToString());
writer.WriteElementString("Customer", nest.Customer);
writer.WriteElementString("DateCreated", nest.DateCreated.ToString());
writer.WriteElementString("DateLastModified", nest.DateLastModified.ToString());
writer.WriteStartElement("DefaultPlate");
writer.WriteElementString("Size", nest.PlateDefaults.Size.ToString());
writer.WriteElementString("Thickness", nest.PlateDefaults.Thickness.ToString());
writer.WriteElementString("Quadrant", nest.PlateDefaults.Quadrant.ToString());
writer.WriteElementString("PartSpacing", nest.PlateDefaults.PartSpacing.ToString());
writer.WriteStartElement("Material");
writer.WriteElementString("Name", nest.PlateDefaults.Material.Name);
writer.WriteElementString("Grade", nest.PlateDefaults.Material.Grade);
writer.WriteElementString("Density", nest.PlateDefaults.Material.Density.ToString());
writer.WriteEndElement();
writer.WriteStartElement("EdgeSpacing");
writer.WriteElementString("Left", nest.PlateDefaults.EdgeSpacing.Left.ToString());
writer.WriteElementString("Top", nest.PlateDefaults.EdgeSpacing.Top.ToString());
writer.WriteElementString("Right", nest.PlateDefaults.EdgeSpacing.Right.ToString());
writer.WriteElementString("Bottom", nest.PlateDefaults.EdgeSpacing.Bottom.ToString());
writer.WriteEndElement();
writer.WriteElementString("Notes", Uri.EscapeDataString(nest.Notes));
writer.WriteEndElement(); // DefaultPlate
writer.WriteEndElement(); // Nest
writer.WriteEndDocument();
writer.Flush();
writer.Close();
stream.Position = 0;
var entry = zipArchive.CreateEntry("info");
using (var entryStream = entry.Open())
{
stream.CopyTo(entryStream);
}
}
private void AddPlates()
{
int num = 1;
foreach (var plate in nest.Plates)
{
var stream = new MemoryStream();
var name = string.Format("plate-{0}", num.ToString().PadLeft(3, '0'));
WritePlate(stream, plate);
var entry = zipArchive.CreateEntry(name);
using (var entryStream = entry.Open())
{
stream.CopyTo(entryStream);
}
num++;
}
}
private void AddPlateInfo()
{
var stream = new MemoryStream();
var writer = XmlWriter.Create(stream, new XmlWriterSettings()
{
Indent = true
});
writer.WriteStartDocument();
writer.WriteStartElement("Plates");
writer.WriteAttributeString("count", nest.Plates.Count.ToString());
for (int i = 0; i < nest.Plates.Count; ++i)
{
var plate = nest.Plates[i];
writer.WriteStartElement("Plate");
writer.WriteAttributeString("id", (i + 1).ToString());
writer.WriteElementString("Quadrant", plate.Quadrant.ToString());
writer.WriteElementString("Thickness", plate.Thickness.ToString());
writer.WriteElementString("Size", plate.Size.ToString());
writer.WriteElementString("Qty", plate.Quantity.ToString());
writer.WriteElementString("PartSpacing", plate.PartSpacing.ToString());
writer.WriteStartElement("Material");
writer.WriteElementString("Name", plate.Material.Name);
writer.WriteElementString("Grade", plate.Material.Grade);
writer.WriteElementString("Density", plate.Material.Density.ToString());
writer.WriteEndElement();
writer.WriteStartElement("EdgeSpacing");
writer.WriteElementString("Left", plate.EdgeSpacing.Left.ToString());
writer.WriteElementString("Top", plate.EdgeSpacing.Top.ToString());
writer.WriteElementString("Right", plate.EdgeSpacing.Right.ToString());
writer.WriteElementString("Bottom", plate.EdgeSpacing.Bottom.ToString());
writer.WriteEndElement();
writer.WriteEndElement(); // Plate
writer.Flush();
}
writer.WriteEndElement(); // Plates
writer.WriteEndDocument();
writer.Flush();
writer.Close();
stream.Position = 0;
var entry = zipArchive.CreateEntry("plate-info");
using (var entryStream = entry.Open())
{
stream.CopyTo(entryStream);
}
}
private void AddDrawings()
{
int num = 1;
foreach (var dwg in nest.Drawings)
{
var stream = new MemoryStream();
var name = string.Format("program-{0}", num.ToString().PadLeft(3, '0'));
WriteDrawing(stream, dwg);
var entry = zipArchive.CreateEntry(name);
using (var entryStream = entry.Open())
{
stream.CopyTo(entryStream);
}
num++;
}
}
private void AddDrawingInfo()
{
var stream = new MemoryStream();
var writer = XmlWriter.Create(stream, new XmlWriterSettings()
{
Indent = true
});
writer.WriteStartDocument();
writer.WriteStartElement("Drawings");
writer.WriteAttributeString("count", nest.Drawings.Count.ToString());
int id = 1;
foreach (var drawing in nest.Drawings)
{
writer.WriteStartElement("Drawing");
writer.WriteAttributeString("id", id.ToString());
writer.WriteAttributeString("name", drawing.Name);
writer.WriteElementString("Customer", drawing.Customer);
writer.WriteElementString("Color", string.Format("{0}, {1}, {2}, {3}", drawing.Color.A, drawing.Color.R, drawing.Color.G, drawing.Color.B));
writer.WriteStartElement("Quantity");
writer.WriteElementString("Required", drawing.Quantity.Required.ToString());
writer.WriteElementString("Nested", drawing.Quantity.Nested.ToString());
writer.WriteEndElement();
writer.WriteStartElement("Material");
writer.WriteElementString("Name", drawing.Material.Name);
writer.WriteElementString("Grade", drawing.Material.Grade);
writer.WriteElementString("Density", drawing.Material.Density.ToString());
writer.WriteEndElement();
writer.WriteStartElement("Source");
writer.WriteElementString("Path", drawing.Source.Path);
writer.WriteElementString("Offset", string.Format("{0}, {1}",
drawing.Source.Offset.X,
drawing.Source.Offset.Y));
writer.WriteEndElement(); // Source
writer.WriteEndElement(); // Drawing
id++;
}
writer.WriteEndElement(); // Drawings
writer.WriteEndDocument();
writer.Flush();
writer.Close();
stream.Position = 0;
var entry = zipArchive.CreateEntry("drawing-info");
using (var entryStream = entry.Open())
{
stream.CopyTo(entryStream);
}
}
private void WritePlate(Stream stream, Plate plate)
{
var writer = new StreamWriter(stream);
writer.AutoFlush = true;
writer.WriteLine("G90");
foreach (var part in plate.Parts)
{
var match = drawingDict.Where(dwg => dwg.Value == part.BaseDrawing).FirstOrDefault();
var id = match.Key;
writer.WriteLine("G00X{0}Y{1}",
part.Location.X.ToString(CoordinateFormat),
part.Location.Y.ToString(CoordinateFormat));
writer.WriteLine("G65P{0}R{1}", id, Angle.ToDegrees(part.Rotation));
}
stream.Position = 0;
}
private void WriteDrawing(Stream stream, Drawing drawing)
{
var program = drawing.Program;
var writer = new StreamWriter(stream);
writer.AutoFlush = true;
writer.WriteLine(program.Mode == Mode.Absolute ? "G90" : "G91");
for (int i = 0; i < drawing.Program.Length; ++i)
{
var code = drawing.Program[i];
writer.WriteLine(GetCodeString(code));
}
stream.Position = 0;
}
private string GetCodeString(ICode code)
{
switch (code.Type)
{
case CodeType.ArcMove:
{
var sb = new StringBuilder();
var arcMove = (ArcMove)code;
var x = System.Math.Round(arcMove.EndPoint.X, OutputPrecision).ToString(CoordinateFormat);
var y = System.Math.Round(arcMove.EndPoint.Y, OutputPrecision).ToString(CoordinateFormat);
var i = System.Math.Round(arcMove.CenterPoint.X, OutputPrecision).ToString(CoordinateFormat);
var j = System.Math.Round(arcMove.CenterPoint.Y, OutputPrecision).ToString(CoordinateFormat);
if (arcMove.Rotation == RotationType.CW)
sb.Append(string.Format("G02X{0}Y{1}I{2}J{3}", x, y, i, j));
else
sb.Append(string.Format("G03X{0}Y{1}I{2}J{3}", x, y, i, j));
if (arcMove.Layer != LayerType.Cut)
sb.Append(GetLayerString(arcMove.Layer));
return sb.ToString();
}
case CodeType.Comment:
{
var comment = (Comment)code;
return ":" + comment.Value;
}
case CodeType.LinearMove:
{
var sb = new StringBuilder();
var linearMove = (LinearMove)code;
sb.Append(string.Format("G01X{0}Y{1}",
System.Math.Round(linearMove.EndPoint.X, OutputPrecision).ToString(CoordinateFormat),
System.Math.Round(linearMove.EndPoint.Y, OutputPrecision).ToString(CoordinateFormat)));
if (linearMove.Layer != LayerType.Cut)
sb.Append(GetLayerString(linearMove.Layer));
return sb.ToString();
}
case CodeType.RapidMove:
{
var rapidMove = (RapidMove)code;
return string.Format("G00X{0}Y{1}",
System.Math.Round(rapidMove.EndPoint.X, OutputPrecision).ToString(CoordinateFormat),
System.Math.Round(rapidMove.EndPoint.Y, OutputPrecision).ToString(CoordinateFormat));
}
case CodeType.SetFeedrate:
{
var setFeedrate = (Feedrate)code;
return "F" + setFeedrate.Value;
}
case CodeType.SetKerf:
{
var setKerf = (Kerf)code;
switch (setKerf.Value)
{
case KerfType.None: return "G40";
case KerfType.Left: return "G41";
case KerfType.Right: return "G42";
}
break;
}
case CodeType.SubProgramCall:
{
var subProgramCall = (SubProgramCall)code;
break;
}
}
return string.Empty;
}
private string GetLayerString(LayerType layer)
{
switch (layer)
{
case LayerType.Display:
return ":DISPLAY";
case LayerType.Leadin:
return ":LEADIN";
case LayerType.Leadout:
return ":LEADOUT";
case LayerType.Scribe:
return ":SCRIBE";
default:
return string.Empty;
}
}
}
}

View File

@@ -1,390 +0,0 @@
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenNest.CNC;
using OpenNest.Geometry;
namespace OpenNest.IO
{
internal sealed class ProgramReader
{
private const int BufferSize = 200;
private int codeIndex;
private CodeBlock block;
private CodeSection section;
private Program program;
private StreamReader reader;
public ProgramReader(Stream stream)
{
reader = new StreamReader(stream);
program = new Program();
}
public Program Read()
{
string line;
while ((line = reader.ReadLine()) != null)
{
block = ParseBlock(line);
ProcessCurrentBlock();
}
return program;
}
private CodeBlock ParseBlock(string line)
{
var block = new CodeBlock();
Code code = null;
for (int i = 0; i < line.Length; ++i)
{
var c = line[i];
if (char.IsLetter(c))
block.Add((code = new Code(c)));
else if (c == ':')
{
block.Add((new Code(c, line.Remove(0, i + 1).Trim())));
break;
}
else if (code != null)
code.Value += c;
}
return block;
}
private void ProcessCurrentBlock()
{
var code = GetFirstCode();
while (code != null)
{
switch (code.Id)
{
case ':':
program.Codes.Add(new Comment(code.Value));
code = GetNextCode();
break;
case 'G':
int value = int.Parse(code.Value);
switch (value)
{
case 0:
case 1:
section = CodeSection.Line;
ReadLine(value == 0);
code = GetCurrentCode();
break;
case 2:
case 3:
section = CodeSection.Arc;
ReadArc(value == 2 ? RotationType.CW : RotationType.CCW);
code = GetCurrentCode();
break;
case 65:
section = CodeSection.SubProgram;
ReadSubProgram();
code = GetCurrentCode();
break;
case 40:
program.Codes.Add(new Kerf() { Value = KerfType.None });
code = GetNextCode();
break;
case 41:
program.Codes.Add(new Kerf() { Value = KerfType.Left });
code = GetNextCode();
break;
case 42:
program.Codes.Add(new Kerf() { Value = KerfType.Right });
code = GetNextCode();
break;
case 90:
program.Mode = Mode.Absolute;
code = GetNextCode();
break;
case 91:
program.Mode = Mode.Incremental;
code = GetNextCode();
break;
default:
code = GetNextCode();
break;
}
break;
case 'F':
program.Codes.Add(new Feedrate() { Value = double.Parse(code.Value) });
code = GetNextCode();
break;
default:
code = GetNextCode();
break;
}
}
}
private void ReadLine(bool isRapid)
{
var line = new LinearMove();
double x = 0;
double y = 0;
var layer = LayerType.Cut;
while (section == CodeSection.Line)
{
var code = GetNextCode();
if (code == null)
{
section = CodeSection.Unknown;
break;
}
switch (code.Id)
{
case 'X':
x = double.Parse(code.Value);
break;
case 'Y':
y = double.Parse(code.Value);
break;
case ':':
{
var value = code.Value.Trim().ToUpper();
switch (value)
{
case "DISPLAY":
layer = LayerType.Display;
break;
case "LEADIN":
layer = LayerType.Leadin;
break;
case "LEADOUT":
layer = LayerType.Leadout;
break;
case "SCRIBE":
layer = LayerType.Scribe;
break;
}
break;
}
default:
section = CodeSection.Unknown;
break;
}
}
if (isRapid)
program.Codes.Add(new RapidMove(x, y));
else
program.Codes.Add(new LinearMove(x, y) { Layer = layer });
}
private void ReadArc(RotationType rotation)
{
double x = 0;
double y = 0;
double i = 0;
double j = 0;
var layer = LayerType.Cut;
while (section == CodeSection.Arc)
{
var code = GetNextCode();
if (code == null)
{
section = CodeSection.Unknown;
break;
}
switch (code.Id)
{
case 'X':
x = double.Parse(code.Value);
break;
case 'Y':
y = double.Parse(code.Value);
break;
case 'I':
i = double.Parse(code.Value);
break;
case 'J':
j = double.Parse(code.Value);
break;
case ':':
{
var value = code.Value.Trim().ToUpper();
switch (value)
{
case "DISPLAY":
layer = LayerType.Display;
break;
case "LEADIN":
layer = LayerType.Leadin;
break;
case "LEADOUT":
layer = LayerType.Leadout;
break;
case "SCRIBE":
layer = LayerType.Scribe;
break;
}
break;
}
default:
section = CodeSection.Unknown;
break;
}
}
program.Codes.Add(new ArcMove()
{
EndPoint = new Vector(x, y),
CenterPoint = new Vector(i, j),
Rotation = rotation,
Layer = layer
});
}
private void ReadSubProgram()
{
var p = 0;
var r = 0.0;
while (section == CodeSection.SubProgram)
{
var code = GetNextCode();
if (code == null)
{
section = CodeSection.Unknown;
break;
}
switch (code.Id)
{
case 'P':
p = int.Parse(code.Value);
break;
case 'R':
r = double.Parse(code.Value);
break;
default:
section = CodeSection.Unknown;
break;
}
}
program.Codes.Add(new SubProgramCall() { Id = p, Rotation = r });
}
private Code GetNextCode()
{
codeIndex++;
if (codeIndex >= block.Count)
return null;
return block[codeIndex];
}
private Code GetCurrentCode()
{
if (codeIndex >= block.Count)
return null;
return block[codeIndex];
}
private Code GetFirstCode()
{
if (block.Count == 0)
return null;
codeIndex = 0;
return block[codeIndex];
}
public void Close()
{
reader.Close();
}
private class Code
{
public Code(char id)
{
Id = id;
Value = string.Empty;
}
public Code(char id, string value)
{
Id = id;
Value = value;
}
public char Id { get; private set; }
public string Value { get; set; }
public override string ToString()
{
return Id + Value;
}
}
private class CodeBlock : List<Code>
{
public void Add(char id, string value)
{
Add(new Code(id, value));
}
public override string ToString()
{
var builder = new StringBuilder();
foreach (var code in this)
builder.Append(code.ToString() + " ");
return builder.ToString();
}
}
private enum CodeSection
{
Unknown,
Arc,
Line,
SubProgram
}
}
}

View File

@@ -14,7 +14,7 @@
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
<ProjectReference Include="..\OpenNest.Engine\OpenNest.Engine.csproj" />
<ProjectReference Include="..\OpenNest.Gpu\OpenNest.Gpu.csproj" />
<PackageReference Include="ACadSharp" Version="3.1.32" />
<ProjectReference Include="..\OpenNest.IO\OpenNest.IO.csproj" />
<PackageReference Include="System.Drawing.Common" Version="8.0.10" />
</ItemGroup>
<ItemGroup>