feat: replace XML nest file format with JSON (v2)

Replace three separate XML metadata files (info, drawing-info,
plate-info) and per-plate G-code placement files with a single
nest.json inside the ZIP archive. Programs remain as G-code text
under a programs/ folder.

This eliminates ~400 lines of hand-written XML read/write code
and fragile ID-based dictionary linking. Now uses System.Text.Json
with DTO records for clean serialization. Also adds Priority and
Constraints fields to drawing serialization (previously omitted).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 18:44:43 -04:00
parent ad50751250
commit c2534ef08b
5 changed files with 1268 additions and 659 deletions
+126
View File
@@ -0,0 +1,126 @@
using System.Collections.Generic;
using System.Text.Json;
namespace OpenNest.IO
{
public static class NestFormat
{
public static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
public record NestDto
{
public int Version { get; init; } = 2;
public string Name { get; init; } = "";
public string Units { get; init; } = "Inches";
public string Customer { get; init; } = "";
public string DateCreated { get; init; } = "";
public string DateLastModified { get; init; } = "";
public string Notes { get; init; } = "";
public PlateDefaultsDto PlateDefaults { get; init; } = new();
public List<DrawingDto> Drawings { get; init; } = new();
public List<PlateDto> Plates { get; init; } = new();
}
public record PlateDefaultsDto
{
public SizeDto Size { get; init; } = new();
public double Thickness { get; init; }
public int Quadrant { get; init; } = 1;
public double PartSpacing { get; init; }
public MaterialDto Material { get; init; } = new();
public SpacingDto EdgeSpacing { get; init; } = new();
}
public record DrawingDto
{
public int Id { get; init; }
public string Name { get; init; } = "";
public string Customer { get; init; } = "";
public ColorDto Color { get; init; } = new();
public QuantityDto Quantity { get; init; } = new();
public int Priority { get; init; }
public ConstraintsDto Constraints { get; init; } = new();
public MaterialDto Material { get; init; } = new();
public SourceDto Source { get; init; } = new();
}
public record PlateDto
{
public int Id { get; init; }
public SizeDto Size { get; init; } = new();
public double Thickness { get; init; }
public int Quadrant { get; init; } = 1;
public int Quantity { get; init; } = 1;
public double PartSpacing { get; init; }
public MaterialDto Material { get; init; } = new();
public SpacingDto EdgeSpacing { get; init; } = new();
public List<PartDto> Parts { get; init; } = new();
}
public record PartDto
{
public int DrawingId { get; init; }
public double X { get; init; }
public double Y { get; init; }
public double Rotation { get; init; }
}
public record SizeDto
{
public double Width { get; init; }
public double Height { get; init; }
}
public record MaterialDto
{
public string Name { get; init; } = "";
public string Grade { get; init; } = "";
public double Density { get; init; }
}
public record SpacingDto
{
public double Left { get; init; }
public double Top { get; init; }
public double Right { get; init; }
public double Bottom { get; init; }
}
public record ColorDto
{
public int A { get; init; } = 255;
public int R { get; init; }
public int G { get; init; }
public int B { get; init; }
}
public record QuantityDto
{
public int Required { get; init; }
}
public record ConstraintsDto
{
public double StepAngle { get; init; }
public double StartAngle { get; init; }
public double EndAngle { get; init; }
public bool Allow180Equivalent { get; init; }
}
public record SourceDto
{
public string Path { get; init; } = "";
public OffsetDto Offset { get; init; } = new();
}
public record OffsetDto
{
public double X { get; init; }
public double Y { get; init; }
}
}
}
+109 -426
View File
@@ -1,45 +1,28 @@
using System;
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 System.Text.Json;
using OpenNest.CNC;
using OpenNest.Geometry;
using OpenNest.Math;
using static OpenNest.IO.NestFormat;
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();
}
private readonly Stream stream;
private readonly ZipArchive zipArchive;
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);
@@ -47,52 +30,12 @@ namespace OpenNest.IO
public Nest Read()
{
const string plateExtensionPattern = "plate-\\d\\d\\d";
const string programExtensionPattern = "program-\\d\\d\\d";
var nestJson = ReadEntry("nest.json");
var dto = JsonSerializer.Deserialize<NestDto>(nestJson, JsonOptions);
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();
var programs = ReadPrograms(dto.Drawings.Count);
var drawingMap = BuildDrawings(dto, programs);
var nest = BuildNest(dto, drawingMap);
zipArchive.Dispose();
stream.Close();
@@ -100,374 +43,114 @@ namespace OpenNest.IO
return nest;
}
private void ReadNestInfo(Stream stream)
private string ReadEntry(string name)
{
var reader = XmlReader.Create(stream);
var spacing = new Spacing();
var entry = zipArchive.GetEntry(name)
?? throw new InvalidDataException($"Nest file is missing required entry '{name}'.");
using var entryStream = entry.Open();
using var reader = new StreamReader(entryStream);
return reader.ReadToEnd();
}
while (reader.Read())
private Dictionary<int, Program> ReadPrograms(int count)
{
var programs = new Dictionary<int, Program>();
for (var i = 1; i <= count; i++)
{
if (!reader.IsStartElement())
continue;
var entry = zipArchive.GetEntry($"programs/program-{i}");
if (entry == null) continue;
switch (reader.Name)
using var entryStream = entry.Open();
var memStream = new MemoryStream();
entryStream.CopyTo(memStream);
memStream.Position = 0;
var reader = new ProgramReader(memStream);
programs[i] = reader.Read();
}
return programs;
}
private Dictionary<int, Drawing> BuildDrawings(NestDto dto, Dictionary<int, Program> programs)
{
var map = new Dictionary<int, Drawing>();
foreach (var d in dto.Drawings)
{
var drawing = new Drawing(d.Name);
drawing.Customer = d.Customer;
drawing.Color = Color.FromArgb(d.Color.A, d.Color.R, d.Color.G, d.Color.B);
drawing.Quantity.Required = d.Quantity.Required;
drawing.Priority = d.Priority;
drawing.Constraints.StepAngle = d.Constraints.StepAngle;
drawing.Constraints.StartAngle = d.Constraints.StartAngle;
drawing.Constraints.EndAngle = d.Constraints.EndAngle;
drawing.Constraints.Allow180Equivalent = d.Constraints.Allow180Equivalent;
drawing.Material = new Material(d.Material.Name, d.Material.Grade, d.Material.Density);
drawing.Source.Path = d.Source.Path;
drawing.Source.Offset = new Vector(d.Source.Offset.X, d.Source.Offset.Y);
if (programs.TryGetValue(d.Id, out var pgm))
drawing.Program = pgm;
map[d.Id] = drawing;
}
return map;
}
private Nest BuildNest(NestDto dto, Dictionary<int, Drawing> drawingMap)
{
var nest = new Nest();
nest.Name = dto.Name;
Units units;
if (Enum.TryParse(dto.Units, true, out units))
nest.Units = units;
nest.Customer = dto.Customer;
nest.DateCreated = DateTime.Parse(dto.DateCreated);
nest.DateLastModified = DateTime.Parse(dto.DateLastModified);
nest.Notes = dto.Notes;
// Plate defaults
var pd = dto.PlateDefaults;
nest.PlateDefaults.Size = new OpenNest.Geometry.Size(pd.Size.Width, pd.Size.Height);
nest.PlateDefaults.Thickness = pd.Thickness;
nest.PlateDefaults.Quadrant = pd.Quadrant;
nest.PlateDefaults.PartSpacing = pd.PartSpacing;
nest.PlateDefaults.Material = new Material(pd.Material.Name, pd.Material.Grade, pd.Material.Density);
nest.PlateDefaults.EdgeSpacing = new Spacing(pd.EdgeSpacing.Left, pd.EdgeSpacing.Bottom, pd.EdgeSpacing.Right, pd.EdgeSpacing.Top);
// Drawings
foreach (var d in drawingMap.OrderBy(k => k.Key))
nest.Drawings.Add(d.Value);
// Plates
foreach (var p in dto.Plates.OrderBy(p => p.Id))
{
var plate = new Plate();
plate.Size = new OpenNest.Geometry.Size(p.Size.Width, p.Size.Height);
plate.Thickness = p.Thickness;
plate.Quadrant = p.Quadrant;
plate.Quantity = p.Quantity;
plate.PartSpacing = p.PartSpacing;
plate.Material = new Material(p.Material.Name, p.Material.Grade, p.Material.Density);
plate.EdgeSpacing = new Spacing(p.EdgeSpacing.Left, p.EdgeSpacing.Bottom, p.EdgeSpacing.Right, p.EdgeSpacing.Top);
foreach (var partDto in p.Parts)
{
case "Nest":
nest.Name = reader["name"];
break;
if (!drawingMap.TryGetValue(partDto.DrawingId, out var dwg))
continue;
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;
var part = new Part(dwg);
part.Rotate(partDto.Rotation);
part.Offset(new Vector(partDto.X, partDto.Y));
plate.Parts.Add(part);
}
nest.Plates.Add(plate);
}
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
return nest;
}
}
}
+132 -233
View File
@@ -1,32 +1,22 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Xml;
using System.Text.Json;
using OpenNest.CNC;
using OpenNest.Math;
using static OpenNest.IO.NestFormat;
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)
@@ -37,27 +27,21 @@ namespace OpenNest.IO
public bool Write(string file)
{
this.nest.DateLastModified = DateTime.Now;
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();
}
using var fileStream = new FileStream(file, FileMode.Create);
using var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create);
WriteNestJson(zipArchive);
WritePrograms(zipArchive);
return true;
}
private void SetDrawingIds()
{
int id = 1;
var id = 1;
foreach (var drawing in nest.Drawings)
{
drawingDict.Add(id, drawing);
@@ -65,241 +49,156 @@ namespace OpenNest.IO
}
}
private void AddNestInfo()
private void WriteNestJson(ZipArchive zipArchive)
{
var stream = new MemoryStream();
var writer = XmlWriter.Create(stream, new XmlWriterSettings()
{
Indent = true
});
var dto = BuildNestDto();
var json = JsonSerializer.Serialize(dto, JsonOptions);
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);
}
var entry = zipArchive.CreateEntry("nest.json");
using var stream = entry.Open();
using var writer = new StreamWriter(stream, Encoding.UTF8);
writer.Write(json);
}
private void AddPlates()
private NestDto BuildNestDto()
{
int num = 1;
foreach (var plate in nest.Plates)
return new NestDto
{
var stream = new MemoryStream();
var name = string.Format("plate-{0}", num.ToString().PadLeft(3, '0'));
Version = 2,
Name = nest.Name ?? "",
Units = nest.Units.ToString(),
Customer = nest.Customer ?? "",
DateCreated = nest.DateCreated.ToString("o"),
DateLastModified = nest.DateLastModified.ToString("o"),
Notes = nest.Notes ?? "",
PlateDefaults = BuildPlateDefaultsDto(),
Drawings = BuildDrawingDtos(),
Plates = BuildPlateDtos()
};
}
WritePlate(stream, plate);
var entry = zipArchive.CreateEntry(name);
using (var entryStream = entry.Open())
private PlateDefaultsDto BuildPlateDefaultsDto()
{
var pd = nest.PlateDefaults;
return new PlateDefaultsDto
{
Size = new SizeDto { Width = pd.Size.Width, Height = pd.Size.Height },
Thickness = pd.Thickness,
Quadrant = pd.Quadrant,
PartSpacing = pd.PartSpacing,
Material = new MaterialDto
{
stream.CopyTo(entryStream);
Name = pd.Material.Name ?? "",
Grade = pd.Material.Grade ?? "",
Density = pd.Material.Density
},
EdgeSpacing = new SpacingDto
{
Left = pd.EdgeSpacing.Left,
Top = pd.EdgeSpacing.Top,
Right = pd.EdgeSpacing.Right,
Bottom = pd.EdgeSpacing.Bottom
}
num++;
}
};
}
private void AddPlateInfo()
private List<DrawingDto> BuildDrawingDtos()
{
var stream = new MemoryStream();
var writer = XmlWriter.Create(stream, new XmlWriterSettings()
var list = new List<DrawingDto>();
foreach (var kvp in drawingDict.OrderBy(k => k.Key))
{
Indent = true
});
var d = kvp.Value;
list.Add(new DrawingDto
{
Id = kvp.Key,
Name = d.Name ?? "",
Customer = d.Customer ?? "",
Color = new ColorDto { A = d.Color.A, R = d.Color.R, G = d.Color.G, B = d.Color.B },
Quantity = new QuantityDto { Required = d.Quantity.Required },
Priority = d.Priority,
Constraints = new ConstraintsDto
{
StepAngle = d.Constraints.StepAngle,
StartAngle = d.Constraints.StartAngle,
EndAngle = d.Constraints.EndAngle,
Allow180Equivalent = d.Constraints.Allow180Equivalent
},
Material = new MaterialDto
{
Name = d.Material.Name ?? "",
Grade = d.Material.Grade ?? "",
Density = d.Material.Density
},
Source = new SourceDto
{
Path = d.Source.Path ?? "",
Offset = new OffsetDto { X = d.Source.Offset.X, Y = d.Source.Offset.Y }
}
});
}
return list;
}
writer.WriteStartDocument();
writer.WriteStartElement("Plates");
writer.WriteAttributeString("count", nest.Plates.Count.ToString());
for (int i = 0; i < nest.Plates.Count; ++i)
private List<PlateDto> BuildPlateDtos()
{
var list = new List<PlateDto>();
for (var 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())
var parts = new List<PartDto>();
foreach (var part in plate.Parts)
{
stream.CopyTo(entryStream);
var match = drawingDict.Where(dwg => dwg.Value == part.BaseDrawing).FirstOrDefault();
parts.Add(new PartDto
{
DrawingId = match.Key,
X = part.Location.X,
Y = part.Location.Y,
Rotation = part.Rotation
});
}
num++;
list.Add(new PlateDto
{
Id = i + 1,
Size = new SizeDto { Width = plate.Size.Width, Height = plate.Size.Height },
Thickness = plate.Thickness,
Quadrant = plate.Quadrant,
Quantity = plate.Quantity,
PartSpacing = plate.PartSpacing,
Material = new MaterialDto
{
Name = plate.Material.Name ?? "",
Grade = plate.Material.Grade ?? "",
Density = plate.Material.Density
},
EdgeSpacing = new SpacingDto
{
Left = plate.EdgeSpacing.Left,
Top = plate.EdgeSpacing.Top,
Right = plate.EdgeSpacing.Right,
Bottom = plate.EdgeSpacing.Bottom
},
Parts = parts
});
}
return list;
}
private void AddDrawingInfo()
private void WritePrograms(ZipArchive zipArchive)
{
var stream = new MemoryStream();
var writer = XmlWriter.Create(stream, new XmlWriterSettings()
foreach (var kvp in drawingDict.OrderBy(k => k.Key))
{
Indent = true
});
var name = $"programs/program-{kvp.Key}";
var stream = new MemoryStream();
WriteDrawing(stream, kvp.Value);
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())
{
var entry = zipArchive.CreateEntry(name);
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;
@@ -308,7 +207,7 @@ namespace OpenNest.IO
writer.WriteLine(program.Mode == Mode.Absolute ? "G90" : "G91");
for (int i = 0; i < drawing.Program.Length; ++i)
for (var i = 0; i < drawing.Program.Length; ++i)
{
var code = drawing.Program[i];
writer.WriteLine(GetCodeString(code));