mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-25 21:01:07 -04:00
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:
1 parent
03b3ed47b2
commit
ca153a1c20
8 files changed
+603
-178
No files matched your search
@@ -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}";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
Reference in new issue
Block a user