From 6e8469be4b62869822a55908bdfd5e71deebedf2 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Sun, 1 Feb 2026 13:18:23 -0500 Subject: [PATCH] refactor: Extract duplicate code in MainForm and CutListTools Consolidate duplicate logic to reduce code smells identified by Roslyn Bridge: - Extract FlushPendingEdits() helper from Save() and Run() methods - Simplify ClearData() to delegate to LoadDocumentData() - Extract ConvertParts(), ConvertStockBins(), RunPackingAlgorithm() helpers - Move DTO classes to separate Models.cs file Co-Authored-By: Claude Opus 4.5 --- CutList.Mcp/CutListTools.cs | 219 +++++++++--------------------------- CutList.Mcp/Models.cs | 83 ++++++++++++++ CutList/Forms/MainForm.cs | 18 ++- 3 files changed, 143 insertions(+), 177 deletions(-) create mode 100644 CutList.Mcp/Models.cs diff --git a/CutList.Mcp/CutListTools.cs b/CutList.Mcp/CutListTools.cs index 285e176..05f2574 100644 --- a/CutList.Mcp/CutListTools.cs +++ b/CutList.Mcp/CutListTools.cs @@ -31,49 +31,15 @@ public static class CutListTools { try { - // Convert parts to BinItems - var binItems = new List(); - foreach (var part in parts) - { - double length = ParseLength(part.Length); - if (length <= 0) - { - return new CutListResult - { - Success = false, - Error = $"Invalid part length: {part.Length} for part '{part.Name}'" - }; - } + var (binItems, partsError) = ConvertParts(parts); + if (partsError != null) + return new CutListResult { Success = false, Error = partsError }; - for (int i = 0; i < part.Quantity; i++) - { - binItems.Add(new BinItem(part.Name, length)); - } - } + var (multiBins, binsError) = ConvertStockBins(stockBins); + if (binsError != null) + return new CutListResult { Success = false, Error = binsError }; - // Convert stock bins to MultiBins - var multiBins = new List(); - foreach (var bin in stockBins) - { - double length = ParseLength(bin.Length); - if (length <= 0) - { - return new CutListResult - { - Success = false, - Error = $"Invalid bin length: {bin.Length}" - }; - } - - multiBins.Add(new MultiBin(length, bin.Quantity, bin.Priority)); - } - - // Run the packing algorithm - var engine = new MultiBinEngine(); - engine.SetBins(multiBins); - engine.Spacing = kerf; - - var packResult = engine.Pack(binItems); + var packResult = RunPackingAlgorithm(binItems!, multiBins!, kerf); // Convert results var resultBins = new List(); @@ -119,7 +85,7 @@ public static class CutListTools Summary = new CutListSummary { TotalBinsUsed = packResult.Bins.Count, - TotalPartsPlaced = binItems.Count - packResult.ItemsNotUsed.Count, + TotalPartsPlaced = binItems!.Count - packResult.ItemsNotUsed.Count, TotalPartsNotPlaced = packResult.ItemsNotUsed.Count, TotalStockLength = FormatLength(totalStockUsed), TotalStockLengthInches = totalStockUsed, @@ -214,49 +180,15 @@ public static class CutListTools { try { - // Convert parts to BinItems - var binItems = new List(); - foreach (var part in parts) - { - double length = ParseLength(part.Length); - if (length <= 0) - { - return new CutListReportResult - { - Success = false, - Error = $"Invalid part length: {part.Length} for part '{part.Name}'" - }; - } + var (binItems, partsError) = ConvertParts(parts); + if (partsError != null) + return new CutListReportResult { Success = false, Error = partsError }; - for (int i = 0; i < part.Quantity; i++) - { - binItems.Add(new BinItem(part.Name, length)); - } - } + var (multiBins, binsError) = ConvertStockBins(stockBins); + if (binsError != null) + return new CutListReportResult { Success = false, Error = binsError }; - // Convert stock bins to MultiBins - var multiBins = new List(); - foreach (var bin in stockBins) - { - double length = ParseLength(bin.Length); - if (length <= 0) - { - return new CutListReportResult - { - Success = false, - Error = $"Invalid bin length: {bin.Length}" - }; - } - - multiBins.Add(new MultiBin(length, bin.Quantity, bin.Priority)); - } - - // Run the packing algorithm - var engine = new MultiBinEngine(); - engine.SetBins(multiBins); - engine.Spacing = kerf; - - var packResult = engine.Pack(binItems); + var packResult = RunPackingAlgorithm(binItems!, multiBins!, kerf); // Determine file path var outputPath = string.IsNullOrWhiteSpace(filePath) @@ -272,7 +204,7 @@ public static class CutListTools Success = true, FilePath = Path.GetFullPath(outputPath), TotalBins = packResult.Bins.Count, - TotalParts = binItems.Count - packResult.ItemsNotUsed.Count, + TotalParts = binItems!.Count - packResult.ItemsNotUsed.Count, PartsNotPlaced = packResult.ItemsNotUsed.Count }; } @@ -286,6 +218,43 @@ public static class CutListTools } } + private static (List? Items, string? Error) ConvertParts(PartInput[] parts) + { + var binItems = new List(); + foreach (var part in parts) + { + double length = ParseLength(part.Length); + if (length <= 0) + return (null, $"Invalid part length: {part.Length} for part '{part.Name}'"); + + for (int i = 0; i < part.Quantity; i++) + binItems.Add(new BinItem(part.Name, length)); + } + return (binItems, null); + } + + private static (List? Bins, string? Error) ConvertStockBins(StockBinInput[] stockBins) + { + var multiBins = new List(); + foreach (var bin in stockBins) + { + double length = ParseLength(bin.Length); + if (length <= 0) + return (null, $"Invalid bin length: {bin.Length}"); + + multiBins.Add(new MultiBin(length, bin.Quantity, bin.Priority)); + } + return (multiBins, null); + } + + private static Core.Nesting.Result RunPackingAlgorithm(List items, List bins, double kerf) + { + var engine = new MultiBinEngine(); + engine.SetBins(bins); + engine.Spacing = kerf; + return engine.Pack(items); + } + private static double ParseLength(string input) { if (string.IsNullOrWhiteSpace(input)) @@ -304,85 +273,3 @@ public static class CutListTools return ArchUnits.FormatFromInches(inches); } } - -// Input models -public class PartInput -{ - public string Name { get; set; } = string.Empty; - public string Length { get; set; } = string.Empty; - public int Quantity { get; set; } = 1; -} - -public class StockBinInput -{ - public string Length { get; set; } = string.Empty; - public int Quantity { get; set; } = -1; // -1 = unlimited - public int Priority { get; set; } = 25; // Lower = used first -} - -// Output models -public class CutListResult -{ - public bool Success { get; set; } - public string? Error { get; set; } - public List Bins { get; set; } = new(); - public List UnusedItems { get; set; } = new(); - public CutListSummary? Summary { get; set; } -} - -public class ResultBin -{ - public string Length { get; set; } = string.Empty; - public double LengthInches { get; set; } - public string UsedLength { get; set; } = string.Empty; - public double UsedLengthInches { get; set; } - public string RemainingLength { get; set; } = string.Empty; - public double RemainingLengthInches { get; set; } - public double Utilization { get; set; } - public List Items { get; set; } = new(); -} - -public class ResultItem -{ - public string Name { get; set; } = string.Empty; - public string Length { get; set; } = string.Empty; - public double LengthInches { get; set; } -} - -public class CutListSummary -{ - public int TotalBinsUsed { get; set; } - public int TotalPartsPlaced { get; set; } - public int TotalPartsNotPlaced { get; set; } - public string TotalStockLength { get; set; } = string.Empty; - public double TotalStockLengthInches { get; set; } - public string TotalWaste { get; set; } = string.Empty; - public double TotalWasteInches { get; set; } - public double OverallUtilization { get; set; } -} - -public class ParseLengthResult -{ - public bool Success { get; set; } - public string? Error { get; set; } - public double Inches { get; set; } - public string? Formatted { get; set; } -} - -public class FormatLengthResult -{ - public bool Success { get; set; } - public string? Error { get; set; } - public string? Formatted { get; set; } - public double Inches { get; set; } -} - -public class CutListReportResult -{ - public bool Success { get; set; } - public string? Error { get; set; } - public string? FilePath { get; set; } - public int TotalBins { get; set; } - public int TotalParts { get; set; } - public int PartsNotPlaced { get; set; } -} diff --git a/CutList.Mcp/Models.cs b/CutList.Mcp/Models.cs new file mode 100644 index 0000000..20bf3ca --- /dev/null +++ b/CutList.Mcp/Models.cs @@ -0,0 +1,83 @@ +namespace CutList.Mcp; + +// Input models +public class PartInput +{ + public string Name { get; set; } = string.Empty; + public string Length { get; set; } = string.Empty; + public int Quantity { get; set; } = 1; +} + +public class StockBinInput +{ + public string Length { get; set; } = string.Empty; + public int Quantity { get; set; } = -1; // -1 = unlimited + public int Priority { get; set; } = 25; // Lower = used first +} + +// Output models +public class CutListResult +{ + public bool Success { get; set; } + public string? Error { get; set; } + public List Bins { get; set; } = new(); + public List UnusedItems { get; set; } = new(); + public CutListSummary? Summary { get; set; } +} + +public class ResultBin +{ + public string Length { get; set; } = string.Empty; + public double LengthInches { get; set; } + public string UsedLength { get; set; } = string.Empty; + public double UsedLengthInches { get; set; } + public string RemainingLength { get; set; } = string.Empty; + public double RemainingLengthInches { get; set; } + public double Utilization { get; set; } + public List Items { get; set; } = new(); +} + +public class ResultItem +{ + public string Name { get; set; } = string.Empty; + public string Length { get; set; } = string.Empty; + public double LengthInches { get; set; } +} + +public class CutListSummary +{ + public int TotalBinsUsed { get; set; } + public int TotalPartsPlaced { get; set; } + public int TotalPartsNotPlaced { get; set; } + public string TotalStockLength { get; set; } = string.Empty; + public double TotalStockLengthInches { get; set; } + public string TotalWaste { get; set; } = string.Empty; + public double TotalWasteInches { get; set; } + public double OverallUtilization { get; set; } +} + +public class ParseLengthResult +{ + public bool Success { get; set; } + public string? Error { get; set; } + public double Inches { get; set; } + public string? Formatted { get; set; } +} + +public class FormatLengthResult +{ + public bool Success { get; set; } + public string? Error { get; set; } + public string? Formatted { get; set; } + public double Inches { get; set; } +} + +public class CutListReportResult +{ + public bool Success { get; set; } + public string? Error { get; set; } + public string? FilePath { get; set; } + public int TotalBins { get; set; } + public int TotalParts { get; set; } + public int PartsNotPlaced { get; set; } +} diff --git a/CutList/Forms/MainForm.cs b/CutList/Forms/MainForm.cs index 71d02f3..95824b2 100644 --- a/CutList/Forms/MainForm.cs +++ b/CutList/Forms/MainForm.cs @@ -137,11 +137,7 @@ namespace CutList.Forms public void ClearData() { - parts = new BindingList(); - bins = new BindingList(); - - itemBindingSource.DataSource = parts; - binInputItemBindingSource.DataSource = bins; + LoadDocumentData(new List(), new List()); } // Event handler delegates to presenter @@ -156,21 +152,21 @@ namespace CutList.Forms dataGridView2.AutoResizeColumns(); } - private void Save() + private void FlushPendingEdits() { - // Flush any in-cell edits that haven't committed yet dataGridView1.EndEdit(); dataGridView2.EndEdit(); + } + private void Save() + { + FlushPendingEdits(); presenter.SaveDocument(); } private void Run() { - // Flush any in-cell edits that haven't committed yet - dataGridView1.EndEdit(); - dataGridView2.EndEdit(); - + FlushPendingEdits(); presenter.Run(); }