using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace OpenNest.IO.Bom;
///
/// One drawing a BOM puts in a drawing set: a drawing file with one material and
/// thickness, its BOM rows' total quantity and its canonical library package.
///
public sealed class BomSetDrawing
{
internal BomSetDrawing(
string name,
string material,
double thickness,
long quantity,
string dxfPath,
IReadOnlyList rows,
Drawing drawing,
byte[] package
)
{
Name = name;
Material = material;
Thickness = thickness;
Quantity = quantity;
DxfPath = dxfPath;
Rows = rows;
Drawing = drawing;
Package = package;
}
/// The drawing file's name without its extension.
public string Name { get; }
/// The BOM material, trimmed; blank when the BOM has none.
public string Material { get; }
/// The BOM thickness, or 0 when the BOM has none.
public double Thickness { get; }
/// The total quantity of the rows that name this drawing.
public long Quantity { get; }
public string DxfPath { get; }
/// The BOM rows this drawing came from, in BOM order.
public IReadOnlyList Rows { get; }
public Drawing Drawing { get; }
/// The drawing as a canonical .
public byte[] Package { get; }
/// True when the drawing still needs a material or thickness before it can be released.
public bool NeedsDetails => Material.Length == 0 || Thickness <= 0;
/// "PT01 (items 3, 7)", or just the name when the rows have no item numbers.
public string Label
{
get
{
var items = Rows.Where(r => r.ItemNum != null).Select(r => r.ItemNum.Value.ToString()).ToList();
return items.Count == 0
? Name
: $"{Name} (item{(items.Count == 1 ? "" : "s")} {string.Join(", ", items)})";
}
}
}
/// A BOM row that does not reach the set, and why.
public sealed record BomSetSkip(BomPartRow Row, string Name, string Reason);
///
/// The drawings a BOM puts in a drawing set and the rows it leaves out. Unlike
/// Create Nests, rows with no material or thickness are kept: the set editor and
/// Edit Drawing fix them before release.
///
public sealed class BomSetDrawings
{
private BomSetDrawings(IReadOnlyList drawings, IReadOnlyList skipped)
{
Drawings = drawings;
Skipped = skipped;
}
/// The drawings in the order their first row appears in the BOM.
public IReadOnlyList Drawings { get; }
public IReadOnlyList Skipped { get; }
///
/// Imports each drawing file named by once and packages it in
/// . Rows naming the same file with the same material
/// (ignoring case) and thickness become one drawing needing their total quantity.
/// Rows with no file name, no drawing file or a quantity below 1, and files that
/// fail to import, are skipped with a reason.
///
public static BomSetDrawings Build(IEnumerable rows, Units units)
{
ArgumentNullException.ThrowIfNull(rows);
var skipped = new List();
var kept = new List();
foreach (var row in rows)
{
switch (row.Status)
{
case BomRowStatus.NoFileName:
skipped.Add(new BomSetSkip(row, RowName(row), "no file name"));
break;
case BomRowStatus.NoDrawing:
skipped.Add(new BomSetSkip(row, RowName(row), "no drawing file found"));
break;
case BomRowStatus.NeedsQuantity:
skipped.Add(new BomSetSkip(row, RowName(row), "quantity is below 1"));
break;
default:
kept.Add(row);
break;
}
}
var drawings = new List();
var imported = new Dictionary(
StringComparer.OrdinalIgnoreCase
);
var groups = kept.GroupBy(r => (
Path: r.DxfPath,
Material: (r.Material ?? "").Trim().ToUpperInvariant(),
Thickness: ThicknessOf(r)
));
foreach (var group in groups)
{
var first = group.First();
if (!imported.TryGetValue(first.DxfPath, out var file))
{
file = Import(first.DxfPath, units);
imported[first.DxfPath] = file;
}
if (file.Error != null)
{
skipped.AddRange(group.Select(r => new BomSetSkip(r, RowName(r), file.Error)));
continue;
}
drawings.Add(
new BomSetDrawing(
Path.GetFileNameWithoutExtension(first.DxfPath),
(first.Material ?? "").Trim(),
group.Key.Thickness,
group.Sum(r => (long)(r.Qty ?? 1)),
first.DxfPath,
group.ToList(),
file.Drawing,
file.Package
)
);
}
return new BomSetDrawings(drawings, skipped);
}
private static (Drawing, byte[], string) Import(string path, Units units)
{
if (!File.Exists(path))
return (null, null, "drawing file not found");
try
{
var drawing = CadImporter.ImportDrawing(path);
return (drawing, DrawingPackage.Write(drawing, units), null);
}
catch (Exception ex)
{
return (null, null, $"could not import the drawing: {ex.Message}");
}
}
private static double ThicknessOf(BomPartRow row) =>
row.Thickness is double t && double.IsFinite(t) && t > 0 ? t : 0;
private static string RowName(BomPartRow row)
{
if (!string.IsNullOrWhiteSpace(row.FileName))
return row.FileName.Trim();
if (row.ItemNum != null)
return $"Item {row.ItemNum}";
return string.IsNullOrWhiteSpace(row.Description) ? "(unnamed row)" : row.Description.Trim();
}
}