From 0ac72fad7056b60af02d7e4cce291464d3e7bcf9 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Mon, 5 Oct 2026 17:52:32 -0400 Subject: [PATCH] fix(bom): combine BOM rows that use the same drawing in one nest When two BOM rows named the same drawing file and shared a material and thickness (the same part in two subassemblies), Create Nests imported the file twice. Nest.Drawings is a set keyed by drawing name, so the second drawing was dropped with its quantity: PT01 x2 plus PT01 x3 gave a nest needing 2, while the Groups tab showed 5. Each drawing file is now imported once and needs the total of its rows. Build_CombinesRowsThatUseTheSameDrawing required [2, 1] where [5, 1] was expected against the previous commit. Project Memory: 44293f97-a027-4c29-bc2d-bb789c796746 --- OpenNest.IO/Bom/BomNestBuilder.cs | 14 +++++++++----- OpenNest.Tests/Bom/BomNestBuilderTests.cs | 16 ++++++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/OpenNest.IO/Bom/BomNestBuilder.cs b/OpenNest.IO/Bom/BomNestBuilder.cs index d7cd475..35f75c0 100644 --- a/OpenNest.IO/Bom/BomNestBuilder.cs +++ b/OpenNest.IO/Bom/BomNestBuilder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using OpenNest.Geometry; namespace OpenNest.IO.Bom; @@ -48,9 +49,9 @@ public static class BomNestBuilder /// Creates a nest named " - thickness material". /// 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. + /// thickness are set. Each drawing file is imported once, needing the + /// total quantity of the rows that name it (a blank quantity counts as + /// 1). The nest gets one plate when at least one drawing imported. /// public static BomNestBuildResult Build( BomImportGroup group, @@ -83,8 +84,11 @@ public static class BomNestBuilder plate.EdgeTop ); - foreach (var part in group.Parts) + // Rows naming the same drawing file become one drawing that needs + // their total: the nest's drawing set is keyed by drawing name. + foreach (var rows in group.Parts.GroupBy(p => p.DxfPath, StringComparer.OrdinalIgnoreCase)) { + var part = rows.First(); if (!File.Exists(part.DxfPath)) { errors.Add($"{part.FileName}: DXF file not found"); @@ -95,7 +99,7 @@ public static class BomNestBuilder { var drawing = CadImporter.ImportDrawing( part.DxfPath, - new CadImportOptions { Quantity = part.Qty ?? 1 } + new CadImportOptions { Quantity = rows.Sum(p => p.Qty ?? 1) } ); drawing.Material = new Material(material); nest.Drawings.Add(drawing); diff --git a/OpenNest.Tests/Bom/BomNestBuilderTests.cs b/OpenNest.Tests/Bom/BomNestBuilderTests.cs index 98075fa..5f572ee 100644 --- a/OpenNest.Tests/Bom/BomNestBuilderTests.cs +++ b/OpenNest.Tests/Bom/BomNestBuilderTests.cs @@ -61,6 +61,22 @@ public sealed class BomNestBuilderTests : IDisposable Assert.All(drawings, d => Assert.Equal("Stainless", d.Material.Name)); } + [Fact] + public void Build_CombinesRowsThatUseTheSameDrawing() + { + var first = Row("PT01", qty: 2); + var second = Row("PT01", qty: 3); + second.FileName = "pt01.dxf"; + + var result = BomNestBuilder.Build(Group(first, second, Row("PT02", qty: 1)), Plate(), "Job", null); + + Assert.Empty(result.Errors); + var nest = Assert.IsType(result.Nest); + 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)); + } + [Fact] public void Build_ReportsMissingDrawingAndKeepsTheOthers() {