From 02d15dea9c9de0db0883118610ae72a37b231854 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 27 Mar 2026 17:33:39 -0400 Subject: [PATCH] feat: add BomImportForm file browsing and analysis logic Task 8: BrowseBom_Click auto-fills DXF folder and derives job name by stripping ' BOM' suffix; BrowseDxf_Click opens folder browser; Analyze_Click reads BOM via BomReader, runs BomAnalyzer, populates DataGridView with material/thickness/parts/qty columns, updates summary label with skipped/unmatched counts, and enables Create Nests button. Co-Authored-By: Claude Sonnet 4.6 --- OpenNest/Forms/BomImportForm.cs | 89 +++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/OpenNest/Forms/BomImportForm.cs b/OpenNest/Forms/BomImportForm.cs index f86cbf1..d8705b5 100644 --- a/OpenNest/Forms/BomImportForm.cs +++ b/OpenNest/Forms/BomImportForm.cs @@ -25,14 +25,103 @@ namespace OpenNest.Forms private void BrowseBom_Click(object sender, EventArgs e) { + using var dlg = new OpenFileDialog + { + Title = "Select BOM File", + Filter = "Excel Files|*.xlsx|All Files|*.*", + FilterIndex = 1, + }; + + if (dlg.ShowDialog(this) != DialogResult.OK) + return; + + txtBomFile.Text = dlg.FileName; + + // Default DXF folder to the same directory as the BOM file + if (string.IsNullOrWhiteSpace(txtDxfFolder.Text)) + txtDxfFolder.Text = Path.GetDirectoryName(dlg.FileName); + + // Derive job name by stripping " BOM" suffix and extension + if (string.IsNullOrWhiteSpace(txtJobName.Text)) + { + var name = Path.GetFileNameWithoutExtension(dlg.FileName); + if (name.EndsWith(" BOM", StringComparison.OrdinalIgnoreCase)) + name = name.Substring(0, name.Length - 4).TrimEnd(); + txtJobName.Text = name; + } } private void BrowseDxf_Click(object sender, EventArgs e) { + using var dlg = new FolderBrowserDialog + { + Description = "Select DXF Folder", + SelectedPath = txtDxfFolder.Text, + }; + + if (dlg.ShowDialog(this) != DialogResult.OK) + return; + + txtDxfFolder.Text = dlg.SelectedPath; } private void Analyze_Click(object sender, EventArgs e) { + if (!File.Exists(txtBomFile.Text)) + { + MessageBox.Show("BOM file does not exist.", "Validation Error", + MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + try + { + List items; + using (var reader = new BomReader(txtBomFile.Text)) + items = reader.GetItems(); + + _analysis = BomAnalyzer.Analyze(items, txtDxfFolder.Text); + + PopulateGrid(_analysis); + UpdateSummary(_analysis); + btnCreateNests.Enabled = _analysis.Groups.Count > 0; + } + catch (Exception ex) + { + MessageBox.Show($"Error reading BOM: {ex.Message}", "Error", + MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void PopulateGrid(BomAnalysis analysis) + { + var table = new DataTable(); + table.Columns.Add("Material", typeof(string)); + table.Columns.Add("Thickness", typeof(string)); + table.Columns.Add("Parts", typeof(int)); + table.Columns.Add("Total Qty", typeof(int)); + + foreach (var group in analysis.Groups) + { + var partCount = group.Parts.Count; + var totalQty = group.Parts.Sum(p => p.Item.Qty ?? 0); + table.Rows.Add(group.Material, group.Thickness.ToString("0.###"), partCount, totalQty); + } + + dgvGroups.DataSource = table; + } + + private void UpdateSummary(BomAnalysis analysis) + { + var parts = new List(); + if (analysis.Skipped.Count > 0) + parts.Add($"{analysis.Skipped.Count} skipped"); + if (analysis.Unmatched.Count > 0) + parts.Add($"{analysis.Unmatched.Count} unmatched"); + + lblSummary.Text = parts.Count > 0 + ? string.Join(", ", parts) + : string.Empty; } private void CreateNests_Click(object sender, EventArgs e)