refactor(bom): build import rows, groups and nests in OpenNest.IO

Move the BOM import dialog's row building, grouping and per-group nest
construction out of BomImportForm into OpenNest.IO/Bom so they can be
tested on Linux:

- BomPartRow (now public) and BomImportRows.Build, a verbatim move of
  the form's BuildPartRows (still via BomAnalyzer).
- BomImportGroups.Build: one grouping used by both the Groups tab and
  Create Nests, which each carried their own copy. Create Nests now
  creates nests in the Groups tab's order (material, then thickness).
- BomNestBuilder.Build: the nest for one group (saved defaults first,
  then the group's plate size, spacing, material and thickness; one
  drawing per row with the row's quantity, 1 when blank).

The form keeps the file dialogs, grids, EditNestForm windows and the
completion message. Behavior is otherwise unchanged; the two row-status
defects found while planning are fixed in the following commits.
This commit is contained in:
aj committed 2026-10-05 17:45:16 -04:00
1 parent 03b3ed47b2
commit ca153a1c20
8 files changed
+603 -178

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.IO.Bom;
/// <summary>
/// The rows of one material and thickness; each group becomes one nest.
/// </summary>
public sealed class BomImportGroup
{
public BomImportGroup(string material, double thickness, IReadOnlyList<BomPartRow> parts)
{
Material = material;
Thickness = thickness;
Parts = parts;
}
/// <summary>The material as the group's first row spells it.</summary>
public string Material { get; }
public double Thickness { get; }
public IReadOnlyList<BomPartRow> Parts { get; }
/// <summary>Sum of the rows' quantities; a blank quantity counts as 0.</summary>
public int TotalQty => Parts.Sum(p => p.Qty ?? 0);
public string Key => BomImportGroups.Key(Material, Thickness);
}
/// <summary>
/// Groups importable rows by material and thickness. The Groups tab and
/// Create Nests both use this, so they always agree.
/// </summary>
public static class BomImportGroups
{
/// <summary>
/// Returns the groups of editable rows that have a drawing, a material
/// and a thickness. Material is compared case-insensitively. Groups are
/// ordered by material, then thickness.
/// </summary>
public static List<BomImportGroup> Build(IEnumerable<BomPartRow> rows)
{
return rows.Where(p =>
p.IsEditable
&& !string.IsNullOrWhiteSpace(p.Material)
&& p.Thickness.HasValue
&& !string.IsNullOrWhiteSpace(p.DxfPath)
)
.GroupBy(p => new
{
Material = p.Material.ToUpperInvariant(),
Thickness = p.Thickness.Value,
})
.OrderBy(g => g.First().Material)
.ThenBy(g => g.Key.Thickness)
.Select(g => new BomImportGroup(g.First().Material, g.Key.Thickness, g.ToList()))
.ToList();
}
/// <summary>
/// The key that identifies a group's plate settings across regrouping.
/// </summary>
public static string Key(string material, double thickness) =>
$"{material?.ToUpperInvariant()}|{thickness}";
}
+73
View File
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace OpenNest.IO.Bom;
/// <summary>
/// Builds the import dialog's part rows from the BOM items and the drawing
/// folder.
/// </summary>
public static class BomImportRows
{
/// <summary>
/// Returns one row per BOM item, in BOM order, with the drawing file it
/// matched in <paramref name="dxfFolder"/> and its import status.
/// </summary>
public static List<BomPartRow> Build(List<BomItem> items, string dxfFolder)
{
var analysis = BomAnalyzer.Analyze(items, dxfFolder);
var matchedPaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var group in analysis.Groups)
foreach (var part in group.Parts)
if (part.DxfPath != null)
matchedPaths[part.Item.FileName ?? ""] = part.DxfPath;
var rows = new List<BomPartRow>();
foreach (var item in items)
{
var row = new BomPartRow
{
ItemNum = item.ItemNum,
FileName = item.FileName,
Qty = item.Qty,
Description = item.Description,
Material = item.Material,
Thickness = item.Thickness,
};
if (string.IsNullOrWhiteSpace(item.FileName))
{
row.Status = "Skipped";
row.IsEditable = false;
}
else
{
var lookupName = item.FileName;
if (
lookupName.EndsWith(".dxf", StringComparison.OrdinalIgnoreCase)
|| lookupName.EndsWith(".dwg", StringComparison.OrdinalIgnoreCase)
)
lookupName = Path.GetFileNameWithoutExtension(lookupName);
if (matchedPaths.TryGetValue(lookupName, out var dxfPath))
{
row.DxfPath = dxfPath;
row.Status = "Matched";
row.IsEditable = true;
}
else
{
row.Status = "No DXF";
row.IsEditable = false;
}
}
rows.Add(row);
}
return rows;
}
}
+115
View File
@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.IO;
using OpenNest.Geometry;
namespace OpenNest.IO.Bom;
/// <summary>Plate size and spacing for one group's nest.</summary>
public sealed class BomGroupPlateSettings
{
public double PlateWidth { get; set; }
public double PlateLength { get; set; }
public double PartSpacing { get; set; }
public double EdgeLeft { get; set; }
public double EdgeBottom { get; set; }
public double EdgeRight { get; set; }
public double EdgeTop { get; set; }
}
/// <summary>
/// The nest built for one group, or none when no drawing imported, plus a
/// message for every drawing that could not be imported.
/// </summary>
public sealed class BomNestBuildResult
{
public BomNestBuildResult(Nest nest, IReadOnlyList<string> errors)
{
Nest = nest;
Errors = errors;
}
/// <summary>The new nest, or null when none of the group's drawings imported.</summary>
public Nest Nest { get; }
public IReadOnlyList<string> Errors { get; }
}
/// <summary>Builds the nest for one BOM import group.</summary>
public static class BomNestBuilder
{
/// <summary>
/// Creates a nest named "<paramref name="jobName"/> - thickness material".
/// <paramref name="applySavedDefaults"/> runs first (units, quadrant,
/// plate), then the group's plate size and spacing, material and
/// thickness are set. Each row's drawing is imported with the row's
/// quantity (1 when blank). The nest gets one plate when at least one
/// drawing imported.
/// </summary>
public static BomNestBuildResult Build(
BomImportGroup group,
BomGroupPlateSettings plate,
string jobName,
Action<Nest> applySavedDefaults
)
{
ArgumentNullException.ThrowIfNull(group);
ArgumentNullException.ThrowIfNull(plate);
var material = group.Material;
var thickness = group.Thickness;
var errors = new List<string>();
var nest = new Nest($"{jobName} - {thickness:0.###} {material}");
nest.DateCreated = DateTime.Now;
nest.DateLastModified = DateTime.Now;
// Saved defaults first (units, quadrant, plate), as New does;
// then the group's own plate size and spacing.
applySavedDefaults?.Invoke(nest);
nest.PlateDefaults.Size = new Size(plate.PlateWidth, plate.PlateLength);
nest.Thickness = thickness;
nest.Material = new Material(material);
nest.PlateDefaults.PartSpacing = plate.PartSpacing;
nest.PlateDefaults.EdgeSpacing = new Spacing(
plate.EdgeLeft,
plate.EdgeBottom,
plate.EdgeRight,
plate.EdgeTop
);
foreach (var part in group.Parts)
{
if (!File.Exists(part.DxfPath))
{
errors.Add($"{part.FileName}: DXF file not found");
continue;
}
try
{
var drawing = CadImporter.ImportDrawing(
part.DxfPath,
new CadImportOptions { Quantity = part.Qty ?? 1 }
);
drawing.Material = new Material(material);
nest.Drawings.Add(drawing);
}
catch (Exception ex)
{
errors.Add($"{part.FileName}: {ex.Message}");
}
}
if (nest.Drawings.Count == 0)
return new BomNestBuildResult(null, errors);
nest.CreatePlate();
return new BomNestBuildResult(nest, errors);
}
}
+27
View File
@@ -0,0 +1,27 @@
namespace OpenNest.IO.Bom;
/// <summary>
/// One BOM line as the import dialog shows and edits it: the values read
/// from the BOM, the drawing file it resolved to and whether it can be
/// imported.
/// </summary>
public class BomPartRow
{
public int? ItemNum { get; set; }
public string FileName { get; set; }
public int? Qty { get; set; }
public string Description { get; set; }
public string Material { get; set; }
public double? Thickness { get; set; }
public string DxfPath { get; set; }
public string Status { get; set; }
public bool IsEditable { get; set; }
}
@@ -0,0 +1,71 @@
using OpenNest.IO.Bom;
namespace OpenNest.Tests.Bom;
public class BomImportGroupsTests
{
[Fact]
public void Build_GroupsByMaterialIgnoringCaseThenThickness()
{
var rows = new List<BomPartRow>
{
Row("PT01", "Stainless", 0.25, qty: 2),
Row("PT02", "Aluminum", 0.125, qty: 1),
Row("PT03", "STAINLESS", 0.25, qty: null),
Row("PT04", "Stainless", 0.125, qty: 4),
Row("PT05", "Aluminum", 0.0625, qty: 3),
};
var groups = BomImportGroups.Build(rows);
Assert.Equal(
new[] { "Aluminum 0.0625", "Aluminum 0.125", "Stainless 0.125", "Stainless 0.25" },
groups.Select(g => $"{g.Material} {g.Thickness}")
);
var stainless = groups[3];
Assert.Equal(new[] { "PT01", "PT03" }, stainless.Parts.Select(p => p.FileName));
Assert.Equal(2, stainless.TotalQty);
Assert.Equal(BomImportGroups.Key("stainless", 0.25), stainless.Key);
}
[Fact]
public void Build_LeavesOutRowsThatCannotBeImported()
{
var locked = Row("PT02", "Stainless", 0.25);
locked.IsEditable = false;
var noDrawing = Row("PT05", "Stainless", 0.25);
noDrawing.DxfPath = null;
var rows = new List<BomPartRow>
{
Row("PT01", "Stainless", 0.25),
locked,
Row("PT03", " ", 0.25),
Row("PT04", "Stainless", null),
noDrawing,
};
var groups = BomImportGroups.Build(rows);
var group = Assert.Single(groups);
Assert.Equal("PT01", Assert.Single(group.Parts).FileName);
}
[Fact]
public void Key_IgnoresMaterialCase()
{
Assert.Equal(BomImportGroups.Key("Stainless", 0.25), BomImportGroups.Key("STAINLESS", 0.25));
Assert.NotEqual(BomImportGroups.Key("Stainless", 0.25), BomImportGroups.Key("Stainless", 0.125));
}
private static BomPartRow Row(string fileName, string material, double? thickness, int? qty = 1) =>
new()
{
FileName = fileName,
Material = material,
Thickness = thickness,
Qty = qty,
DxfPath = $"/drawings/{fileName}.dxf",
Status = "Matched",
IsEditable = true,
};
}
+93
View File
@@ -0,0 +1,93 @@
using OpenNest.IO.Bom;
namespace OpenNest.Tests.Bom;
public sealed class BomImportRowsTests : IDisposable
{
private readonly string folder = Path.Combine(
Path.GetTempPath(),
"bom-rows-" + Guid.NewGuid().ToString("N")
);
public BomImportRowsTests()
{
Directory.CreateDirectory(folder);
}
public void Dispose()
{
if (Directory.Exists(folder))
Directory.Delete(folder, recursive: true);
}
[Fact]
public void Build_MatchesRowsToDrawingFilesInBomOrder()
{
Touch("PT01.dxf");
Touch("PT03.dwg");
var items = new List<BomItem>
{
Item("PT01", qty: 2, itemNum: 1, description: "Gusset"),
Item("PT02", qty: 3, itemNum: 2),
Item(null, qty: 4, itemNum: 3),
Item("PT03", qty: null, itemNum: 4),
};
var rows = BomImportRows.Build(items, folder);
Assert.Equal(new[] { 1, 2, 3, 4 }, rows.Select(r => r.ItemNum ?? 0));
Assert.Equal(new[] { "Matched", "No DXF", "Skipped", "Matched" }, rows.Select(r => r.Status));
Assert.Equal(new[] { true, false, false, true }, rows.Select(r => r.IsEditable));
Assert.Equal(Path.Combine(folder, "PT01.dxf"), rows[0].DxfPath);
Assert.Null(rows[1].DxfPath);
Assert.Null(rows[2].DxfPath);
Assert.Equal(Path.Combine(folder, "PT03.dwg"), rows[3].DxfPath);
Assert.Equal("Gusset", rows[0].Description);
Assert.Equal(2, rows[0].Qty);
Assert.Null(rows[3].Qty);
Assert.Equal("Stainless", rows[0].Material);
Assert.Equal(0.25, rows[0].Thickness);
}
[Fact]
public void Build_MatchesFileNamesIgnoringCase()
{
Touch("PT01.dxf");
var rows = BomImportRows.Build(new List<BomItem> { Item("pt01") }, folder);
Assert.Equal("Matched", rows[0].Status);
Assert.Equal(Path.Combine(folder, "PT01.dxf"), rows[0].DxfPath);
}
[Fact]
public void Build_WithMissingFolder_FindsNoDrawings()
{
var missing = Path.Combine(folder, "missing");
var rows = BomImportRows.Build(new List<BomItem> { Item("PT01") }, missing);
Assert.Equal("No DXF", rows[0].Status);
Assert.False(rows[0].IsEditable);
Assert.Null(rows[0].DxfPath);
}
private void Touch(string name) => File.WriteAllText(Path.Combine(folder, name), "");
private static BomItem Item(
string? fileName,
int? qty = 1,
int? itemNum = null,
string? description = null
) =>
new()
{
ItemNum = itemNum,
FileName = fileName,
Qty = qty,
Description = description,
Material = "Stainless",
Thickness = 0.25,
};
}
+130
View File
@@ -0,0 +1,130 @@
using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.IO;
using OpenNest.IO.Bom;
namespace OpenNest.Tests.Bom;
public sealed class BomNestBuilderTests : IDisposable
{
private readonly string folder = Path.Combine(
Path.GetTempPath(),
"bom-nests-" + Guid.NewGuid().ToString("N")
);
public BomNestBuilderTests()
{
Directory.CreateDirectory(folder);
}
public void Dispose()
{
if (Directory.Exists(folder))
Directory.Delete(folder, recursive: true);
}
[Fact]
public void Build_AppliesSavedDefaultsThenGroupSettings()
{
var group = Group(Row("PT01", qty: 5), Row("PT02", qty: null));
var plate = Plate();
var result = BomNestBuilder.Build(
group,
plate,
"Job 42",
nest =>
{
nest.Units = Units.Millimeters;
nest.PlateDefaults.Quadrant = 3;
nest.PlateDefaults.Size = new Size(1, 1);
nest.PlateDefaults.PartSpacing = 9;
}
);
Assert.Empty(result.Errors);
var nest = Assert.IsType<Nest>(result.Nest);
Assert.Equal("Job 42 - 0.25 Stainless", nest.Name);
Assert.Equal(Units.Millimeters, nest.Units);
Assert.Equal(3, nest.PlateDefaults.Quadrant);
Assert.Equal(60, nest.PlateDefaults.Size.Width);
Assert.Equal(120, nest.PlateDefaults.Size.Length);
Assert.Equal(0.2, nest.PlateDefaults.PartSpacing);
Assert.Equal(new[] { 0.1, 0.2, 0.3, 0.4 }, Edges(nest.PlateDefaults.EdgeSpacing));
Assert.Equal(0.25, nest.Thickness);
Assert.Equal("Stainless", nest.Material.Name);
Assert.Single(nest.Plates);
var drawings = nest.Drawings.OrderBy(d => d.Name).ToList();
Assert.Equal(new[] { "PT01", "PT02" }, drawings.Select(d => d.Name));
Assert.Equal(new[] { 5, 1 }, drawings.Select(d => d.Quantity.Required));
Assert.All(drawings, d => Assert.Equal("Stainless", d.Material.Name));
}
[Fact]
public void Build_ReportsMissingDrawingAndKeepsTheOthers()
{
var missing = Row("PT09");
File.Delete(missing.DxfPath);
var result = BomNestBuilder.Build(Group(Row("PT01"), missing), Plate(), "Job", null);
Assert.Equal("PT09: DXF file not found", Assert.Single(result.Errors));
var nest = Assert.IsType<Nest>(result.Nest);
Assert.Equal("PT01", Assert.Single(nest.Drawings).Name);
}
[Fact]
public void Build_WithNoImportedDrawing_ReturnsNoNest()
{
var unreadable = Row("PT01");
File.WriteAllText(unreadable.DxfPath, "not a drawing");
var result = BomNestBuilder.Build(Group(unreadable), Plate(), "Job", null);
Assert.Null(result.Nest);
Assert.StartsWith("PT01: ", Assert.Single(result.Errors));
}
private BomPartRow Row(string name, int? qty = 1) =>
new()
{
FileName = name,
Qty = qty,
Material = "Stainless",
Thickness = 0.25,
DxfPath = WriteSquare(name),
Status = "Matched",
IsEditable = true,
};
private static BomImportGroup Group(params BomPartRow[] rows) => new("Stainless", 0.25, rows);
private static BomGroupPlateSettings Plate() =>
new()
{
PlateWidth = 60,
PlateLength = 120,
PartSpacing = 0.2,
EdgeLeft = 0.1,
EdgeBottom = 0.2,
EdgeRight = 0.3,
EdgeTop = 0.4,
};
private static double[] Edges(Spacing spacing) =>
new[] { spacing.Left, spacing.Bottom, spacing.Right, spacing.Top };
private string WriteSquare(string name)
{
var shape = new Shape();
shape.Entities.Add(new Line(new Vector(0, 0), new Vector(2, 0)));
shape.Entities.Add(new Line(new Vector(2, 0), new Vector(2, 2)));
shape.Entities.Add(new Line(new Vector(2, 2), new Vector(0, 2)));
shape.Entities.Add(new Line(new Vector(0, 2), new Vector(0, 0)));
var path = Path.Combine(folder, name + ".dxf");
Dxf.ExportProgram(ConvertGeometry.ToProgram(shape), path);
return path;
}
}
+28 -178
View File
@@ -6,7 +6,6 @@ using System.IO;
using System.Linq;
using System.Windows.Forms;
using OpenNest.Data;
using OpenNest.Geometry;
using OpenNest.IO;
using OpenNest.IO.Bom;
@@ -15,7 +14,7 @@ namespace OpenNest.Forms
public partial class BomImportForm : Form
{
private List<BomPartRow> _parts;
private Dictionary<string, GroupSettings> _groupSettings;
private Dictionary<string, BomGroupPlateSettings> _groupSettings;
private bool _suppressRegroup;
private NestDefaults _defaults;
@@ -25,7 +24,7 @@ namespace OpenNest.Forms
{
InitializeComponent();
_parts = new List<BomPartRow>();
_groupSettings = new Dictionary<string, GroupSettings>();
_groupSettings = new Dictionary<string, BomGroupPlateSettings>();
_defaults = MainForm.LoadSavedNestDefaults(out _);
ApplyDefaults();
}
@@ -101,8 +100,8 @@ namespace OpenNest.Forms
using (var reader = new BomReader(txtBomFile.Text))
items = reader.GetItems();
var analysis = BomAnalyzer.Analyze(items, txtDxfFolder.Text);
BuildPartRows(items, analysis);
_parts = BomImportRows.Build(items, txtDxfFolder.Text);
_groupSettings.Clear();
PopulatePartsGrid();
RebuildGroups();
UpdateSummary();
@@ -120,62 +119,6 @@ namespace OpenNest.Forms
}
}
private void BuildPartRows(List<BomItem> items, BomAnalysis analysis)
{
var matchedPaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var group in analysis.Groups)
foreach (var part in group.Parts)
if (part.DxfPath != null)
matchedPaths[part.Item.FileName ?? ""] = part.DxfPath;
_parts = new List<BomPartRow>();
foreach (var item in items)
{
var row = new BomPartRow
{
ItemNum = item.ItemNum,
FileName = item.FileName,
Qty = item.Qty,
Description = item.Description,
Material = item.Material,
Thickness = item.Thickness,
};
if (string.IsNullOrWhiteSpace(item.FileName))
{
row.Status = "Skipped";
row.IsEditable = false;
}
else
{
var lookupName = item.FileName;
if (
lookupName.EndsWith(".dxf", StringComparison.OrdinalIgnoreCase)
|| lookupName.EndsWith(".dwg", StringComparison.OrdinalIgnoreCase)
)
lookupName = Path.GetFileNameWithoutExtension(lookupName);
if (matchedPaths.TryGetValue(lookupName, out var dxfPath))
{
row.DxfPath = dxfPath;
row.Status = "Matched";
row.IsEditable = true;
}
else
{
row.Status = "No DXF";
row.IsEditable = false;
}
}
_parts.Add(row);
}
_groupSettings.Clear();
}
#endregion
#region Parts Tab
@@ -273,18 +216,7 @@ namespace OpenNest.Forms
? l
: _defaults.Size.Length;
var groups = _parts
.Where(p =>
p.IsEditable && !string.IsNullOrWhiteSpace(p.Material) && p.Thickness.HasValue
)
.GroupBy(p => new
{
Material = p.Material.ToUpperInvariant(),
Thickness = p.Thickness.Value,
})
.OrderBy(g => g.First().Material)
.ThenBy(g => g.Key.Thickness)
.ToList();
var groups = BomImportGroups.Build(_parts);
var table = new DataTable();
table.Columns.Add("Material", typeof(string));
@@ -301,17 +233,13 @@ namespace OpenNest.Forms
foreach (var group in groups)
{
var material = group.First().Material;
var thickness = group.Key.Thickness;
var key = GroupKey(material, thickness);
var existing = _groupSettings.TryGetValue(key, out var gs);
var existing = _groupSettings.TryGetValue(group.Key, out var gs);
table.Rows.Add(
material,
thickness,
group.Count(),
group.Sum(p => p.Qty ?? 0),
group.Material,
group.Thickness,
group.Parts.Count,
group.TotalQty,
existing ? gs.PlateWidth : defaultWidth,
existing ? gs.PlateLength : defaultLength,
existing ? gs.PartSpacing : _defaults.PartSpacing,
@@ -346,9 +274,9 @@ namespace OpenNest.Forms
{
var material = row["Material"]?.ToString() ?? "";
var thickness = row["Thickness"] is double t ? t : 0;
var key = GroupKey(material, thickness);
var key = BomImportGroups.Key(material, thickness);
_groupSettings[key] = new GroupSettings
_groupSettings[key] = new BomGroupPlateSettings
{
PlateWidth = row["Plate Width"] is double pw
? pw
@@ -373,9 +301,6 @@ namespace OpenNest.Forms
}
}
private static string GroupKey(string material, double thickness) =>
$"{material?.ToUpperInvariant()}|{thickness}";
#endregion
#region Summary
@@ -417,19 +342,7 @@ namespace OpenNest.Forms
? dl
: _defaults.Size.Length;
var groups = _parts
.Where(p =>
p.IsEditable
&& !string.IsNullOrWhiteSpace(p.Material)
&& p.Thickness.HasValue
&& !string.IsNullOrWhiteSpace(p.DxfPath)
)
.GroupBy(p => new
{
Material = p.Material.ToUpperInvariant(),
Thickness = p.Thickness.Value,
})
.ToList();
var groups = BomImportGroups.Build(_parts);
if (groups.Count == 0)
{
@@ -448,66 +361,27 @@ namespace OpenNest.Forms
foreach (var group in groups)
{
var material = group.First().Material;
var thickness = group.Key.Thickness;
var key = GroupKey(material, thickness);
var hasSettings = _groupSettings.TryGetValue(key, out var gs);
var plateWidth = hasSettings ? gs.PlateWidth : defaultWidth;
var plateLength = hasSettings ? gs.PlateLength : defaultLength;
var partSpacing = hasSettings ? gs.PartSpacing : _defaults.PartSpacing;
var edgeLeft = hasSettings ? gs.EdgeLeft : _defaults.EdgeSpacing.Left;
var edgeBottom = hasSettings ? gs.EdgeBottom : _defaults.EdgeSpacing.Bottom;
var edgeRight = hasSettings ? gs.EdgeRight : _defaults.EdgeSpacing.Right;
var edgeTop = hasSettings ? gs.EdgeTop : _defaults.EdgeSpacing.Top;
var nestName = $"{jobName} - {thickness:0.###} {material}";
var nest = new Nest(nestName);
nest.DateCreated = DateTime.Now;
nest.DateLastModified = DateTime.Now;
// Saved defaults first (units, quadrant, plate), as New does;
// then the group's own plate size and spacing.
_defaults.ApplyTo(nest);
nest.PlateDefaults.Size = new Geometry.Size(plateWidth, plateLength);
nest.Thickness = thickness;
nest.Material = new Material(material);
nest.PlateDefaults.PartSpacing = partSpacing;
nest.PlateDefaults.EdgeSpacing = new Spacing(
edgeLeft,
edgeBottom,
edgeRight,
edgeTop
);
foreach (var part in group)
if (!_groupSettings.TryGetValue(group.Key, out var plate))
{
if (!File.Exists(part.DxfPath))
plate = new BomGroupPlateSettings
{
importErrors.Add($"{part.FileName}: DXF file not found");
continue;
}
try
{
var drawing = CadImporter.ImportDrawing(
part.DxfPath,
new CadImportOptions { Quantity = part.Qty ?? 1 }
);
drawing.Material = new Material(material);
nest.Drawings.Add(drawing);
}
catch (Exception ex)
{
importErrors.Add($"{part.FileName}: {ex.Message}");
}
PlateWidth = defaultWidth,
PlateLength = defaultLength,
PartSpacing = _defaults.PartSpacing,
EdgeLeft = _defaults.EdgeSpacing.Left,
EdgeBottom = _defaults.EdgeSpacing.Bottom,
EdgeRight = _defaults.EdgeSpacing.Right,
EdgeTop = _defaults.EdgeSpacing.Top,
};
}
if (nest.Drawings.Count == 0)
var result = BomNestBuilder.Build(group, plate, jobName, _defaults.ApplyTo);
importErrors.AddRange(result.Errors);
if (result.Nest == null)
continue;
nest.CreatePlate();
var editForm = new EditNestForm(nest);
var editForm = new EditNestForm(result.Nest);
editForm.MdiParent = MdiParentForm;
editForm.Show();
editForm.PlateView.ZoomToFit();
@@ -538,28 +412,4 @@ namespace OpenNest.Forms
Close();
}
}
internal class BomPartRow
{
public int? ItemNum { get; set; }
public string FileName { get; set; }
public int? Qty { get; set; }
public string Description { get; set; }
public string Material { get; set; }
public double? Thickness { get; set; }
public string DxfPath { get; set; }
public string Status { get; set; }
public bool IsEditable { get; set; }
}
internal class GroupSettings
{
public double PlateWidth { get; set; }
public double PlateLength { get; set; }
public double PartSpacing { get; set; }
public double EdgeLeft { get; set; }
public double EdgeBottom { get; set; }
public double EdgeRight { get; set; }
public double EdgeTop { get; set; }
}
}