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 <noreply@anthropic.com>
This commit is contained in:
2026-02-01 13:18:23 -05:00
parent 046976c429
commit 6e8469be4b
3 changed files with 143 additions and 177 deletions

View File

@@ -31,49 +31,15 @@ public static class CutListTools
{ {
try try
{ {
// Convert parts to BinItems var (binItems, partsError) = ConvertParts(parts);
var binItems = new List<BinItem>(); if (partsError != null)
foreach (var part in parts) return new CutListResult { Success = false, Error = partsError };
{
double length = ParseLength(part.Length);
if (length <= 0)
{
return new CutListResult
{
Success = false,
Error = $"Invalid part length: {part.Length} for part '{part.Name}'"
};
}
for (int i = 0; i < part.Quantity; i++) var (multiBins, binsError) = ConvertStockBins(stockBins);
{ if (binsError != null)
binItems.Add(new BinItem(part.Name, length)); return new CutListResult { Success = false, Error = binsError };
}
}
// Convert stock bins to MultiBins var packResult = RunPackingAlgorithm(binItems!, multiBins!, kerf);
var multiBins = new List<MultiBin>();
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);
// Convert results // Convert results
var resultBins = new List<ResultBin>(); var resultBins = new List<ResultBin>();
@@ -119,7 +85,7 @@ public static class CutListTools
Summary = new CutListSummary Summary = new CutListSummary
{ {
TotalBinsUsed = packResult.Bins.Count, TotalBinsUsed = packResult.Bins.Count,
TotalPartsPlaced = binItems.Count - packResult.ItemsNotUsed.Count, TotalPartsPlaced = binItems!.Count - packResult.ItemsNotUsed.Count,
TotalPartsNotPlaced = packResult.ItemsNotUsed.Count, TotalPartsNotPlaced = packResult.ItemsNotUsed.Count,
TotalStockLength = FormatLength(totalStockUsed), TotalStockLength = FormatLength(totalStockUsed),
TotalStockLengthInches = totalStockUsed, TotalStockLengthInches = totalStockUsed,
@@ -214,49 +180,15 @@ public static class CutListTools
{ {
try try
{ {
// Convert parts to BinItems var (binItems, partsError) = ConvertParts(parts);
var binItems = new List<BinItem>(); if (partsError != null)
foreach (var part in parts) return new CutListReportResult { Success = false, Error = partsError };
{
double length = ParseLength(part.Length);
if (length <= 0)
{
return new CutListReportResult
{
Success = false,
Error = $"Invalid part length: {part.Length} for part '{part.Name}'"
};
}
for (int i = 0; i < part.Quantity; i++) var (multiBins, binsError) = ConvertStockBins(stockBins);
{ if (binsError != null)
binItems.Add(new BinItem(part.Name, length)); return new CutListReportResult { Success = false, Error = binsError };
}
}
// Convert stock bins to MultiBins var packResult = RunPackingAlgorithm(binItems!, multiBins!, kerf);
var multiBins = new List<MultiBin>();
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);
// Determine file path // Determine file path
var outputPath = string.IsNullOrWhiteSpace(filePath) var outputPath = string.IsNullOrWhiteSpace(filePath)
@@ -272,7 +204,7 @@ public static class CutListTools
Success = true, Success = true,
FilePath = Path.GetFullPath(outputPath), FilePath = Path.GetFullPath(outputPath),
TotalBins = packResult.Bins.Count, TotalBins = packResult.Bins.Count,
TotalParts = binItems.Count - packResult.ItemsNotUsed.Count, TotalParts = binItems!.Count - packResult.ItemsNotUsed.Count,
PartsNotPlaced = packResult.ItemsNotUsed.Count PartsNotPlaced = packResult.ItemsNotUsed.Count
}; };
} }
@@ -286,6 +218,43 @@ public static class CutListTools
} }
} }
private static (List<BinItem>? Items, string? Error) ConvertParts(PartInput[] parts)
{
var binItems = new List<BinItem>();
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<MultiBin>? Bins, string? Error) ConvertStockBins(StockBinInput[] stockBins)
{
var multiBins = new List<MultiBin>();
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<BinItem> items, List<MultiBin> bins, double kerf)
{
var engine = new MultiBinEngine();
engine.SetBins(bins);
engine.Spacing = kerf;
return engine.Pack(items);
}
private static double ParseLength(string input) private static double ParseLength(string input)
{ {
if (string.IsNullOrWhiteSpace(input)) if (string.IsNullOrWhiteSpace(input))
@@ -304,85 +273,3 @@ public static class CutListTools
return ArchUnits.FormatFromInches(inches); 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<ResultBin> Bins { get; set; } = new();
public List<ResultItem> 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<ResultItem> 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; }
}

83
CutList.Mcp/Models.cs Normal file
View File

@@ -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<ResultBin> Bins { get; set; } = new();
public List<ResultItem> 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<ResultItem> 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; }
}

View File

@@ -137,11 +137,7 @@ namespace CutList.Forms
public void ClearData() public void ClearData()
{ {
parts = new BindingList<PartInputItem>(); LoadDocumentData(new List<PartInputItem>(), new List<BinInputItem>());
bins = new BindingList<BinInputItem>();
itemBindingSource.DataSource = parts;
binInputItemBindingSource.DataSource = bins;
} }
// Event handler delegates to presenter // Event handler delegates to presenter
@@ -156,21 +152,21 @@ namespace CutList.Forms
dataGridView2.AutoResizeColumns(); dataGridView2.AutoResizeColumns();
} }
private void Save() private void FlushPendingEdits()
{ {
// Flush any in-cell edits that haven't committed yet
dataGridView1.EndEdit(); dataGridView1.EndEdit();
dataGridView2.EndEdit(); dataGridView2.EndEdit();
}
private void Save()
{
FlushPendingEdits();
presenter.SaveDocument(); presenter.SaveDocument();
} }
private void Run() private void Run()
{ {
// Flush any in-cell edits that haven't committed yet FlushPendingEdits();
dataGridView1.EndEdit();
dataGridView2.EndEdit();
presenter.Run(); presenter.Run();
} }